1#![forbid(unsafe_code)]
30use crate::convolution::{ConvolutionOptions, HorizontalFilterPass, VerticalConvolutionPass};
31use crate::factory::{Ar30ByteOrder, Rgb30};
32use crate::image_size::ImageSize;
33use crate::image_store::{CheckStoreDensity, ImageStore, ImageStoreMut};
34use crate::math::WeightsGenerator;
35use crate::plan::{
36 AlphaPlanner, Ar30Destructuring, Ar30DestructuringImpl, Ar30Plan, BothAxesConvolvePlan,
37 DefaultPlanner, HorizontalConvolvePlan, NoopPlan, ResampleNearestPlan, Resampling,
38 TrampolineFiltering, VerticalConvolvePlan,
39};
40use crate::threading_policy::ThreadingPolicy;
41use crate::validation::PicScaleError;
42use crate::{
43 CbCr8ImageStore, CbCr16ImageStore, CbCrF32ImageStore, Planar8ImageStore, Planar16ImageStore,
44 PlanarF32ImageStore, ResamplingFunction, ResamplingPlan, Rgb8ImageStore, Rgb16ImageStore,
45 RgbF32ImageStore, Rgba8ImageStore, Rgba16ImageStore, RgbaF32ImageStore,
46};
47use std::fmt::Debug;
48use std::marker::PhantomData;
49use std::sync::Arc;
50
51#[derive(Debug, Copy, Clone)]
52pub struct Scaler {
54 pub(crate) function: ResamplingFunction,
55 pub(crate) threading_policy: ThreadingPolicy,
56 pub workload_strategy: WorkloadStrategy,
57 pub(crate) multi_step_upscaling: bool,
58 pub(crate) supersampling: bool,
59}
60
61#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Default)]
63pub enum WorkloadStrategy {
64 PreferQuality,
66 #[default]
68 PreferSpeed,
69}
70
71impl Scaler {
72 pub fn new(filter: ResamplingFunction) -> Self {
77 Scaler {
78 function: filter,
79 threading_policy: ThreadingPolicy::Single,
80 workload_strategy: WorkloadStrategy::default(),
81 multi_step_upscaling: false,
82 supersampling: false,
83 }
84 }
85
86 pub fn set_workload_strategy(&mut self, workload_strategy: WorkloadStrategy) -> Self {
90 self.workload_strategy = workload_strategy;
91 *self
92 }
93
94 pub fn set_multi_step_upsampling(&mut self, value: bool) -> Self {
111 self.multi_step_upscaling = value;
112 *self
113 }
114
115 pub fn set_supersampling(&mut self, value: bool) -> Self {
134 self.supersampling = value;
135 *self
136 }
137}
138
139impl Scaler {
140 pub(crate) fn build_single_step_plan<
141 T: Clone + Copy + Debug + Send + Sync + Default + WeightsGenerator<W> + 'static,
142 W,
143 const N: usize,
144 >(
145 &self,
146 source_size: ImageSize,
147 destination_size: ImageSize,
148 bit_depth: usize,
149 ) -> Result<Arc<Resampling<T, N>>, PicScaleError>
150 where
151 for<'a> ImageStore<'a, T, N>:
152 VerticalConvolutionPass<T, W, N> + HorizontalFilterPass<T, W, N>,
153 for<'a> ImageStoreMut<'a, T, N>: CheckStoreDensity,
154 {
155 if self.function == ResamplingFunction::Nearest {
156 return Ok(Arc::new(ResampleNearestPlan {
157 source_size,
158 target_size: destination_size,
159 threading_policy: self.threading_policy,
160 _phantom_data: PhantomData,
161 }));
162 }
163
164 let should_do_horizontal = source_size.width != destination_size.width;
165 let should_do_vertical = source_size.height != destination_size.height;
166
167 let options = ConvolutionOptions {
168 workload_strategy: self.workload_strategy,
169 bit_depth,
170 src_size: source_size,
171 dst_size: destination_size,
172 };
173
174 let vertical_plan = if should_do_vertical {
175 let vertical_filters =
176 T::make_weights(self.function, source_size.height, destination_size.height)?;
177 Some(ImageStore::<T, N>::vertical_plan(
178 vertical_filters,
179 self.threading_policy,
180 options,
181 ))
182 } else {
183 None
184 };
185
186 let horizontal_plan = if should_do_horizontal {
187 let horizontal_filters =
188 T::make_weights(self.function, source_size.width, destination_size.width)?;
189 Some(ImageStore::<T, N>::horizontal_plan(
190 horizontal_filters,
191 self.threading_policy,
192 options,
193 ))
194 } else {
195 None
196 };
197
198 match (should_do_vertical, should_do_horizontal) {
199 (true, true) => {
200 let v = vertical_plan.expect("Should have a vertical filter");
201 let h = horizontal_plan.expect("Should have a horizontal filter");
202 let trampoline = Arc::new(TrampolineFiltering {
203 horizontal_filter: h.clone(),
204 vertical_filter: v.clone(),
205 source_size,
206 target_size: destination_size,
207 });
208 Ok(Arc::new(BothAxesConvolvePlan {
209 source_size,
210 target_size: destination_size,
211 horizontal_filter: h,
212 vertical_filter: v,
213 trampoline_filter: trampoline,
214 threading_policy: self.threading_policy,
215 }))
216 }
217 (true, false) => Ok(Arc::new(VerticalConvolvePlan {
218 source_size,
219 target_size: destination_size,
220 vertical_filter: vertical_plan.expect("Should have a vertical filter"),
221 })),
222 (false, true) => Ok(Arc::new(HorizontalConvolvePlan {
223 source_size,
224 target_size: destination_size,
225 horizontal_filter: horizontal_plan.expect("Should have a horizontal filter"),
226 })),
227 (false, false) => Ok(Arc::new(NoopPlan {
228 source_size,
229 target_size: destination_size,
230 _phantom: PhantomData,
231 })),
232 }
233 }
234
235 pub fn plan_planar_resampling(
251 &self,
252 source_size: ImageSize,
253 target_size: ImageSize,
254 ) -> Result<Arc<Resampling<u8, 1>>, PicScaleError> {
255 DefaultPlanner::plan_generic_resize(self, source_size, target_size, 8)
256 }
257
258 pub fn plan_gray_alpha_resampling(
277 &self,
278 source_size: ImageSize,
279 target_size: ImageSize,
280 premultiply_alpha: bool,
281 ) -> Result<Arc<Resampling<u8, 2>>, PicScaleError> {
282 if premultiply_alpha {
283 AlphaPlanner::plan_generic_resize_with_alpha(
284 self,
285 source_size,
286 target_size,
287 8,
288 premultiply_alpha,
289 )
290 } else {
291 DefaultPlanner::plan_generic_resize(self, source_size, target_size, 8)
292 }
293 }
294
295 pub fn plan_cbcr_resampling(
313 &self,
314 source_size: ImageSize,
315 target_size: ImageSize,
316 ) -> Result<Arc<Resampling<u8, 2>>, PicScaleError> {
317 DefaultPlanner::plan_generic_resize(self, source_size, target_size, 8)
318 }
319
320 pub fn plan_rgb_resampling(
338 &self,
339 source_size: ImageSize,
340 target_size: ImageSize,
341 ) -> Result<Arc<Resampling<u8, 3>>, PicScaleError> {
342 DefaultPlanner::plan_generic_resize(self, source_size, target_size, 8)
343 }
344
345 pub fn plan_rgba_resampling(
364 &self,
365 source_size: ImageSize,
366 target_size: ImageSize,
367 premultiply_alpha: bool,
368 ) -> Result<Arc<Resampling<u8, 4>>, PicScaleError> {
369 if premultiply_alpha {
370 AlphaPlanner::plan_generic_resize_with_alpha(
371 self,
372 source_size,
373 target_size,
374 8,
375 premultiply_alpha,
376 )
377 } else {
378 DefaultPlanner::plan_generic_resize(self, source_size, target_size, 8)
379 }
380 }
381
382 pub fn plan_planar_resampling16(
401 &self,
402 source_size: ImageSize,
403 target_size: ImageSize,
404 bit_depth: usize,
405 ) -> Result<Arc<Resampling<u16, 1>>, PicScaleError> {
406 DefaultPlanner::plan_generic_resize(self, source_size, target_size, bit_depth)
407 }
408
409 pub fn plan_planar_resampling_s16(
428 &self,
429 source_size: ImageSize,
430 target_size: ImageSize,
431 bit_depth: usize,
432 ) -> Result<Arc<Resampling<i16, 1>>, PicScaleError> {
433 DefaultPlanner::plan_generic_resize(self, source_size, target_size, bit_depth)
434 }
435
436 pub fn plan_cbcr_resampling16(
456 &self,
457 source_size: ImageSize,
458 target_size: ImageSize,
459 bit_depth: usize,
460 ) -> Result<Arc<Resampling<u16, 2>>, PicScaleError> {
461 DefaultPlanner::plan_generic_resize(self, source_size, target_size, bit_depth)
462 }
463
464 pub fn plan_gray_alpha_resampling16(
485 &self,
486 source_size: ImageSize,
487 target_size: ImageSize,
488 premultiply_alpha: bool,
489 bit_depth: usize,
490 ) -> Result<Arc<Resampling<u16, 2>>, PicScaleError> {
491 if premultiply_alpha {
492 AlphaPlanner::plan_generic_resize_with_alpha(
493 self,
494 source_size,
495 target_size,
496 bit_depth,
497 premultiply_alpha,
498 )
499 } else {
500 self.plan_cbcr_resampling16(source_size, target_size, bit_depth)
501 }
502 }
503
504 pub fn plan_rgb_resampling16(
524 &self,
525 source_size: ImageSize,
526 target_size: ImageSize,
527 bit_depth: usize,
528 ) -> Result<Arc<Resampling<u16, 3>>, PicScaleError> {
529 DefaultPlanner::plan_generic_resize(self, source_size, target_size, bit_depth)
530 }
531
532 pub fn plan_rgba_resampling16(
553 &self,
554 source_size: ImageSize,
555 target_size: ImageSize,
556 premultiply_alpha: bool,
557 bit_depth: usize,
558 ) -> Result<Arc<Resampling<u16, 4>>, PicScaleError> {
559 if premultiply_alpha {
560 AlphaPlanner::plan_generic_resize_with_alpha(
561 self,
562 source_size,
563 target_size,
564 bit_depth,
565 premultiply_alpha,
566 )
567 } else {
568 DefaultPlanner::plan_generic_resize(self, source_size, target_size, bit_depth)
569 }
570 }
571
572 pub fn plan_planar_resampling_f32(
596 &self,
597 source_size: ImageSize,
598 target_size: ImageSize,
599 ) -> Result<Arc<Resampling<f32, 1>>, PicScaleError> {
600 match self.workload_strategy {
601 WorkloadStrategy::PreferQuality => DefaultPlanner::plan_generic_resize::<f32, f64, 1>(
602 self,
603 source_size,
604 target_size,
605 8,
606 ),
607 WorkloadStrategy::PreferSpeed => DefaultPlanner::plan_generic_resize::<f32, f32, 1>(
608 self,
609 source_size,
610 target_size,
611 8,
612 ),
613 }
614 }
615
616 pub fn plan_cbcr_resampling_f32(
641 &self,
642 source_size: ImageSize,
643 target_size: ImageSize,
644 ) -> Result<Arc<dyn ResamplingPlan<f32, 2> + Send + Sync>, PicScaleError> {
645 match self.workload_strategy {
646 WorkloadStrategy::PreferQuality => DefaultPlanner::plan_generic_resize::<f32, f64, 2>(
647 self,
648 source_size,
649 target_size,
650 8,
651 ),
652 WorkloadStrategy::PreferSpeed => DefaultPlanner::plan_generic_resize::<f32, f32, 2>(
653 self,
654 source_size,
655 target_size,
656 8,
657 ),
658 }
659 }
660
661 pub fn plan_gray_alpha_resampling_f32(
689 &self,
690 source_size: ImageSize,
691 target_size: ImageSize,
692 premultiply_alpha: bool,
693 ) -> Result<Arc<Resampling<f32, 2>>, PicScaleError> {
694 if premultiply_alpha {
695 match self.workload_strategy {
696 WorkloadStrategy::PreferQuality => {
697 AlphaPlanner::plan_generic_resize_with_alpha::<f32, f64, 2>(
698 self,
699 source_size,
700 target_size,
701 8,
702 premultiply_alpha,
703 )
704 }
705 WorkloadStrategy::PreferSpeed => {
706 AlphaPlanner::plan_generic_resize_with_alpha::<f32, f32, 2>(
707 self,
708 source_size,
709 target_size,
710 8,
711 premultiply_alpha,
712 )
713 }
714 }
715 } else {
716 self.plan_cbcr_resampling_f32(source_size, target_size)
717 }
718 }
719
720 pub fn plan_rgb_resampling_f32(
745 &self,
746 source_size: ImageSize,
747 target_size: ImageSize,
748 ) -> Result<Arc<Resampling<f32, 3>>, PicScaleError> {
749 match self.workload_strategy {
750 WorkloadStrategy::PreferQuality => DefaultPlanner::plan_generic_resize::<f32, f64, 3>(
751 self,
752 source_size,
753 target_size,
754 8,
755 ),
756 WorkloadStrategy::PreferSpeed => DefaultPlanner::plan_generic_resize::<f32, f32, 3>(
757 self,
758 source_size,
759 target_size,
760 8,
761 ),
762 }
763 }
764
765 pub fn plan_rgba_resampling_f32(
792 &self,
793 source_size: ImageSize,
794 target_size: ImageSize,
795 premultiply_alpha: bool,
796 ) -> Result<Arc<Resampling<f32, 4>>, PicScaleError> {
797 if premultiply_alpha {
798 match self.workload_strategy {
799 WorkloadStrategy::PreferQuality => {
800 AlphaPlanner::plan_generic_resize_with_alpha::<f32, f64, 4>(
801 self,
802 source_size,
803 target_size,
804 8,
805 premultiply_alpha,
806 )
807 }
808 WorkloadStrategy::PreferSpeed => {
809 AlphaPlanner::plan_generic_resize_with_alpha::<f32, f32, 4>(
810 self,
811 source_size,
812 target_size,
813 8,
814 premultiply_alpha,
815 )
816 }
817 }
818 } else {
819 match self.workload_strategy {
820 WorkloadStrategy::PreferQuality => {
821 DefaultPlanner::plan_generic_resize::<f32, f64, 4>(
822 self,
823 source_size,
824 target_size,
825 8,
826 )
827 }
828 WorkloadStrategy::PreferSpeed => {
829 DefaultPlanner::plan_generic_resize::<f32, f32, 4>(
830 self,
831 source_size,
832 target_size,
833 8,
834 )
835 }
836 }
837 }
838 }
839
840 pub fn set_threading_policy(&mut self, threading_policy: ThreadingPolicy) -> Self {
841 self.threading_policy = threading_policy;
842 *self
843 }
844}
845
846impl Scaler {
847 pub(crate) fn plan_resize_ar30<const AR30_ORDER: usize>(
848 &self,
849 ar30_type: Rgb30,
850 source_size: ImageSize,
851 destination_size: ImageSize,
852 ) -> Result<Arc<Resampling<u8, 4>>, PicScaleError> {
853 if self.function == ResamplingFunction::Nearest {
854 return Ok(Arc::new(ResampleNearestPlan {
855 source_size,
856 target_size: destination_size,
857 threading_policy: self.threading_policy,
858 _phantom_data: PhantomData,
859 }));
860 }
861 let inner_plan = self.plan_rgb_resampling16(source_size, destination_size, 10)?;
862 let mut _decomposer: Arc<dyn Ar30Destructuring + Send + Sync> =
863 Arc::new(Ar30DestructuringImpl::<AR30_ORDER> { rgb30: ar30_type });
864 #[cfg(all(target_arch = "x86_64", feature = "avx"))]
865 {
866 if std::arch::is_x86_feature_detected!("avx2") {
867 use crate::avx2::{
868 avx_column_handler_fixed_point_ar30, avx_convolve_horizontal_rgba_rows_4_ar30,
869 avx_convolve_horizontal_rgba_rows_ar30,
870 };
871 use crate::plan::{HorizontalFiltering, VerticalFiltering};
872 let should_do_horizontal = source_size.width != destination_size.width;
873 let should_do_vertical = source_size.height != destination_size.height;
874
875 let vertical_plan = if should_do_vertical {
876 let vertical_filters = u8::make_weights(
877 self.function,
878 source_size.height,
879 destination_size.height,
880 )?;
881 Some(Arc::new(VerticalFiltering {
882 filter_row: match ar30_type {
883 Rgb30::Ar30 => {
884 avx_column_handler_fixed_point_ar30::<
885 { Rgb30::Ar30 as usize },
886 AR30_ORDER,
887 >
888 }
889 Rgb30::Ra30 => {
890 avx_column_handler_fixed_point_ar30::<
891 { Rgb30::Ra30 as usize },
892 AR30_ORDER,
893 >
894 }
895 },
896 filter_weights: vertical_filters
897 .numerical_approximation_i16::<{ crate::support::PRECISION }>(0),
898 threading_policy: self.threading_policy,
899 }))
900 } else {
901 None
902 };
903
904 let horizontal_plan = if should_do_horizontal {
905 let horizontal_filters =
906 u8::make_weights(self.function, source_size.width, destination_size.width)?;
907 Some(Arc::new(HorizontalFiltering {
908 filter_row: match ar30_type {
909 Rgb30::Ar30 => {
910 avx_convolve_horizontal_rgba_rows_ar30::<
911 { Rgb30::Ar30 as usize },
912 AR30_ORDER,
913 >
914 }
915 Rgb30::Ra30 => {
916 avx_convolve_horizontal_rgba_rows_ar30::<
917 { Rgb30::Ra30 as usize },
918 AR30_ORDER,
919 >
920 }
921 },
922 filter_4_rows: Some(match ar30_type {
923 Rgb30::Ar30 => {
924 avx_convolve_horizontal_rgba_rows_4_ar30::<
925 { Rgb30::Ar30 as usize },
926 AR30_ORDER,
927 >
928 }
929 Rgb30::Ra30 => {
930 avx_convolve_horizontal_rgba_rows_4_ar30::<
931 { Rgb30::Ra30 as usize },
932 AR30_ORDER,
933 >
934 }
935 }),
936 threading_policy: self.threading_policy,
937 filter_weights: horizontal_filters
938 .numerical_approximation_i16::<{ crate::support::PRECISION }>(0),
939 }))
940 } else {
941 None
942 };
943
944 return Ok(match (should_do_vertical, should_do_horizontal) {
945 (true, true) => {
946 let v = vertical_plan.expect("Should have vertical plan");
947 let h = horizontal_plan.expect("Should have horizontal plan");
948 let trampoline = Arc::new(TrampolineFiltering {
949 horizontal_filter: h.clone(),
950 vertical_filter: v.clone(),
951 source_size,
952 target_size: destination_size,
953 });
954 Arc::new(BothAxesConvolvePlan {
955 source_size,
956 target_size: destination_size,
957 horizontal_filter: h,
958 vertical_filter: v,
959 trampoline_filter: trampoline,
960 threading_policy: self.threading_policy,
961 })
962 }
963 (true, false) => Arc::new(VerticalConvolvePlan {
964 source_size,
965 target_size: destination_size,
966 vertical_filter: vertical_plan.expect("Should have vertical plan"),
967 }),
968 (false, true) => Arc::new(HorizontalConvolvePlan {
969 source_size,
970 target_size: destination_size,
971 horizontal_filter: horizontal_plan.expect("Should have horizontal plan"),
972 }),
973 (false, false) => Arc::new(NoopPlan {
974 source_size,
975 target_size: destination_size,
976 _phantom: PhantomData,
977 }),
978 });
979 }
980 }
981 Ok(Arc::new(Ar30Plan {
982 source_size,
983 target_size: destination_size,
984 inner_filter: inner_plan,
985 decomposer: _decomposer,
986 }))
987 }
988
989 pub fn plan_ar30_resampling(
1011 &self,
1012 source_size: ImageSize,
1013 target_size: ImageSize,
1014 order: Ar30ByteOrder,
1015 ) -> Result<Arc<Resampling<u8, 4>>, PicScaleError> {
1016 match order {
1017 Ar30ByteOrder::Host => self.plan_resize_ar30::<{ Ar30ByteOrder::Host as usize }>(
1018 Rgb30::Ar30,
1019 source_size,
1020 target_size,
1021 ),
1022 Ar30ByteOrder::Network => self.plan_resize_ar30::<{ Ar30ByteOrder::Network as usize }>(
1023 Rgb30::Ar30,
1024 source_size,
1025 target_size,
1026 ),
1027 }
1028 }
1029
1030 pub fn plan_ra30_resampling(
1052 &self,
1053 source_size: ImageSize,
1054 target_size: ImageSize,
1055 order: Ar30ByteOrder,
1056 ) -> Result<Arc<Resampling<u8, 4>>, PicScaleError> {
1057 match order {
1058 Ar30ByteOrder::Host => self.plan_resize_ar30::<{ Ar30ByteOrder::Host as usize }>(
1059 Rgb30::Ra30,
1060 source_size,
1061 target_size,
1062 ),
1063 Ar30ByteOrder::Network => self.plan_resize_ar30::<{ Ar30ByteOrder::Network as usize }>(
1064 Rgb30::Ra30,
1065 source_size,
1066 target_size,
1067 ),
1068 }
1069 }
1070}
1071
1072#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Default)]
1074pub struct ScalingOptions {
1075 pub resampling_function: ResamplingFunction,
1076 pub premultiply_alpha: bool,
1077 pub threading_policy: ThreadingPolicy,
1078}
1079
1080pub trait ImageStoreScaling<'b, T, const N: usize>
1082where
1083 T: Clone + Copy + Debug,
1084{
1085 fn scale(
1086 &self,
1087 store: &mut ImageStoreMut<'b, T, N>,
1088 options: ScalingOptions,
1089 ) -> Result<(), PicScaleError>;
1090}
1091
1092macro_rules! def_image_scaling_alpha {
1093 ($clazz: ident, $fx_type: ident, $cn: expr) => {
1094 impl<'b> ImageStoreScaling<'b, $fx_type, $cn> for $clazz<'b> {
1095 fn scale(
1096 &self,
1097 store: &mut ImageStoreMut<'b, $fx_type, $cn>,
1098 options: ScalingOptions,
1099 ) -> Result<(), PicScaleError> {
1100 let scaler = Scaler::new(options.resampling_function)
1101 .set_threading_policy(options.threading_policy);
1102 let plan = AlphaPlanner::plan_generic_resize_with_alpha::<$fx_type, f32, $cn>(
1103 &scaler,
1104 self.size(),
1105 store.size(),
1106 store.bit_depth,
1107 options.premultiply_alpha,
1108 )?;
1109 plan.resample(self, store)
1110 }
1111 }
1112 };
1113}
1114
1115macro_rules! def_image_scaling {
1116 ($clazz: ident, $fx_type: ident, $cn: expr) => {
1117 impl<'b> ImageStoreScaling<'b, $fx_type, $cn> for $clazz<'b> {
1118 fn scale(
1119 &self,
1120 store: &mut ImageStoreMut<'b, $fx_type, $cn>,
1121 options: ScalingOptions,
1122 ) -> Result<(), PicScaleError> {
1123 let scaler = Scaler::new(options.resampling_function)
1124 .set_threading_policy(options.threading_policy);
1125 let plan = DefaultPlanner::plan_generic_resize::<$fx_type, f32, $cn>(
1126 &scaler,
1127 self.size(),
1128 store.size(),
1129 store.bit_depth,
1130 )?;
1131 plan.resample(self, store)
1132 }
1133 }
1134 };
1135}
1136
1137def_image_scaling_alpha!(Rgba8ImageStore, u8, 4);
1138def_image_scaling!(Rgb8ImageStore, u8, 3);
1139def_image_scaling!(CbCr8ImageStore, u8, 2);
1140def_image_scaling!(Planar8ImageStore, u8, 1);
1141def_image_scaling!(Planar16ImageStore, u16, 1);
1142def_image_scaling!(CbCr16ImageStore, u16, 2);
1143def_image_scaling!(Rgb16ImageStore, u16, 3);
1144def_image_scaling_alpha!(Rgba16ImageStore, u16, 4);
1145def_image_scaling!(PlanarF32ImageStore, f32, 1);
1146def_image_scaling!(CbCrF32ImageStore, f32, 2);
1147def_image_scaling!(RgbF32ImageStore, f32, 3);
1148def_image_scaling_alpha!(RgbaF32ImageStore, f32, 4);
1149
1150#[cfg(test)]
1151mod tests {
1152 use super::*;
1153
1154 macro_rules! check_rgba8 {
1155 ($dst: expr, $image_width: expr, $max: expr) => {
1156 {
1157 for (y, row) in $dst.chunks_exact($image_width * 4).enumerate() {
1158 for (i, dst) in row.chunks_exact(4).enumerate() {
1159 let diff0 = (dst[0] as i32 - 124).abs();
1160 let diff1 = (dst[1] as i32 - 41).abs();
1161 let diff2 = (dst[2] as i32 - 99).abs();
1162 let diff3 = (dst[3] as i32 - 77).abs();
1163 assert!(
1164 diff0 < $max,
1165 "Diff for channel 0 is expected < {}, but it was {diff0}, at (y: {y}, x: {i})",
1166 $max
1167 );
1168 assert!(
1169 diff1 < $max,
1170 "Diff for channel 1 is expected < {}, but it was {diff1}, at (y: {y}, x: {i})",
1171 $max
1172 );
1173 assert!(
1174 diff2 < $max,
1175 "Diff for channel 2 is expected < {}, but it was {diff2}, at (y: {y}, x: {i})",
1176 $max
1177 );
1178 assert!(
1179 diff3 < $max,
1180 "Diff for channel 3 is expected < {}, but it was {diff3}, at (y: {y}, x: {i})",
1181 $max
1182 );
1183 }
1184 }
1185 }
1186 };
1187 }
1188
1189 macro_rules! check_rgb16 {
1190 ($dst: expr, $image_width: expr, $max: expr) => {
1191 {
1192 for (y, row) in $dst.chunks_exact($image_width * 3).enumerate() {
1193 for (i, dst) in row.chunks_exact(3).enumerate() {
1194 let diff0 = (dst[0] as i32 - 124).abs();
1195 let diff1 = (dst[1] as i32 - 41).abs();
1196 let diff2 = (dst[2] as i32 - 99).abs();
1197 assert!(
1198 diff0 < $max,
1199 "Diff for channel 0 is expected < {}, but it was {diff0}, at (y: {y}, x: {i})",
1200 $max
1201 );
1202 assert!(
1203 diff1 < $max,
1204 "Diff for channel 1 is expected < {}, but it was {diff1}, at (y: {y}, x: {i})",
1205 $max
1206 );
1207 assert!(
1208 diff2 < $max,
1209 "Diff for channel 2 is expected < {}, but it was {diff2}, at (y: {y}, x: {i})",
1210 $max
1211 );
1212 }
1213 }
1214 }
1215 };
1216 }
1217
1218 #[test]
1219 fn check_rgba8_resizing_vertical() {
1220 let image_width = 255;
1221 let image_height = 512;
1222 const CN: usize = 4;
1223 let mut image = vec![0u8; image_height * image_width * CN];
1224 for dst in image.chunks_exact_mut(4) {
1225 dst[0] = 124;
1226 dst[1] = 41;
1227 dst[2] = 99;
1228 dst[3] = 77;
1229 }
1230 let scaler =
1231 Scaler::new(ResamplingFunction::Bilinear).set_threading_policy(ThreadingPolicy::Single);
1232 let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1233 let mut target_store = ImageStoreMut::alloc(image_width, image_height / 2);
1234 let planned = scaler
1235 .plan_rgba_resampling(src_store.size(), target_store.size(), false)
1236 .unwrap();
1237 planned.resample(&src_store, &mut target_store).unwrap();
1238 let target_data = target_store.buffer.borrow();
1239 check_rgba8!(target_data, image_width, 34);
1240 }
1241
1242 #[test]
1243 fn check_rgba8_resizing_both() {
1244 let image_width = 255;
1245 let image_height = 512;
1246 const CN: usize = 4;
1247 let mut image = vec![0u8; image_height * image_width * CN];
1248 for dst in image.chunks_exact_mut(4) {
1249 dst[0] = 124;
1250 dst[1] = 41;
1251 dst[2] = 99;
1252 dst[3] = 77;
1253 }
1254 image[3] = 78;
1255 let mut scaler = Scaler::new(ResamplingFunction::Bilinear);
1256 scaler.set_threading_policy(ThreadingPolicy::Single);
1257 let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1258 let mut target_store = ImageStoreMut::alloc(image_width / 2, image_height / 2);
1259 let planned = scaler
1260 .plan_rgba_resampling(src_store.size(), target_store.size(), false)
1261 .unwrap();
1262 planned.resample(&src_store, &mut target_store).unwrap();
1263 let target_data = target_store.buffer.borrow();
1264 check_rgba8!(target_data, image_width, 34);
1265 }
1266
1267 #[test]
1268 fn check_rgba8_resizing_alpha() {
1269 let image_width = 255;
1270 let image_height = 512;
1271 const CN: usize = 4;
1272 let mut image = vec![0u8; image_height * image_width * CN];
1273 for dst in image.chunks_exact_mut(4) {
1274 dst[0] = 124;
1275 dst[1] = 41;
1276 dst[2] = 99;
1277 dst[3] = 77;
1278 }
1279 image[3] = 78;
1280 let scaler =
1281 Scaler::new(ResamplingFunction::Lanczos3).set_threading_policy(ThreadingPolicy::Single);
1282 let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1283 let mut target_store = ImageStoreMut::alloc(image_width / 2, image_height / 2);
1284 let planned = scaler
1285 .plan_rgba_resampling(src_store.size(), target_store.size(), true)
1286 .unwrap();
1287 planned.resample(&src_store, &mut target_store).unwrap();
1288 let target_data = target_store.buffer.borrow();
1289 check_rgba8!(target_data, image_width, 160);
1290 }
1291
1292 #[test]
1293 fn check_rgb8_resizing_vertical() {
1294 let image_width = 255;
1295 let image_height = 512;
1296 const CN: usize = 3;
1297 let mut image = vec![0u8; image_height * image_width * CN];
1298 for dst in image.chunks_exact_mut(3) {
1299 dst[0] = 124;
1300 dst[1] = 41;
1301 dst[2] = 99;
1302 }
1303 let mut scaler = Scaler::new(ResamplingFunction::Bilinear);
1304 scaler.set_threading_policy(ThreadingPolicy::Single);
1305 let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1306 let mut target_store = ImageStoreMut::alloc(image_width, image_height / 2);
1307 let planned = scaler
1308 .plan_rgb_resampling(src_store.size(), target_store.size())
1309 .unwrap();
1310 planned.resample(&src_store, &mut target_store).unwrap();
1311 let target_data = target_store.buffer.borrow();
1312
1313 check_rgb16!(target_data, image_width, 85);
1314 }
1315
1316 #[test]
1317 fn check_rgb8_resizing_vertical_threading() {
1318 let image_width = 255;
1319 let image_height = 512;
1320 const CN: usize = 3;
1321 let mut image = vec![0u8; image_height * image_width * CN];
1322 for dst in image.chunks_exact_mut(3) {
1323 dst[0] = 124;
1324 dst[1] = 41;
1325 dst[2] = 99;
1326 }
1327 let mut scaler = Scaler::new(ResamplingFunction::Bilinear);
1328 scaler.set_threading_policy(ThreadingPolicy::Adaptive);
1329 let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1330 let mut target_store = ImageStoreMut::alloc(image_width, image_height / 2);
1331 let planned = scaler
1332 .plan_rgb_resampling(src_store.size(), target_store.size())
1333 .unwrap();
1334 planned.resample(&src_store, &mut target_store).unwrap();
1335 let target_data = target_store.buffer.borrow();
1336
1337 check_rgb16!(target_data, image_width, 85);
1338 }
1339
1340 #[test]
1341 fn check_rgba10_resizing_vertical() {
1342 let image_width = 8;
1343 let image_height = 8;
1344 const CN: usize = 4;
1345 let mut image = vec![0u16; image_height * image_width * CN];
1346 for dst in image.chunks_exact_mut(4) {
1347 dst[0] = 124;
1348 dst[1] = 41;
1349 dst[2] = 99;
1350 dst[3] = 77;
1351 }
1352 image[3] = 78;
1353 let scaler =
1354 Scaler::new(ResamplingFunction::Lanczos3).set_threading_policy(ThreadingPolicy::Single);
1355 let mut src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1356 src_store.bit_depth = 10;
1357 let mut target_store = ImageStoreMut::alloc_with_depth(image_width, image_height / 2, 10);
1358 let planned = scaler
1359 .plan_rgba_resampling16(src_store.size(), target_store.size(), true, 10)
1360 .unwrap();
1361 planned.resample(&src_store, &mut target_store).unwrap();
1362 let target_data = target_store.buffer.borrow();
1363
1364 check_rgba8!(target_data, image_width, 60);
1365 }
1366
1367 #[test]
1368 fn check_rgb10_resizing_vertical() {
1369 let image_width = 8;
1370 let image_height = 4;
1371 const CN: usize = 3;
1372 let mut image = vec![0; image_height * image_width * CN];
1373 for dst in image.chunks_exact_mut(3) {
1374 dst[0] = 124;
1375 dst[1] = 41;
1376 dst[2] = 99;
1377 }
1378 let scaler =
1379 Scaler::new(ResamplingFunction::Lanczos3).set_threading_policy(ThreadingPolicy::Single);
1380 let mut src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1381 src_store.bit_depth = 10;
1382 let mut target_store = ImageStoreMut::alloc_with_depth(image_width, image_height / 2, 10);
1383 let planned = scaler
1384 .plan_rgb_resampling16(src_store.size(), target_store.size(), 10)
1385 .unwrap();
1386 planned.resample(&src_store, &mut target_store).unwrap();
1387 let target_data = target_store.buffer.borrow();
1388
1389 check_rgb16!(target_data, image_width, 85);
1390 }
1391
1392 #[test]
1393 fn check_rgb10_resizing_vertical_adaptive() {
1394 let image_width = 8;
1395 let image_height = 4;
1396 const CN: usize = 3;
1397 let mut image = vec![0; image_height * image_width * CN];
1398 for dst in image.chunks_exact_mut(3) {
1399 dst[0] = 124;
1400 dst[1] = 41;
1401 dst[2] = 99;
1402 }
1403 let mut scaler = Scaler::new(ResamplingFunction::Lanczos3);
1404 scaler.set_threading_policy(ThreadingPolicy::Adaptive);
1405 let mut src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1406 src_store.bit_depth = 10;
1407 let mut target_store = ImageStoreMut::alloc_with_depth(image_width, image_height / 2, 10);
1408 let planned = scaler
1409 .plan_rgb_resampling16(src_store.size(), target_store.size(), 10)
1410 .unwrap();
1411 planned.resample(&src_store, &mut target_store).unwrap();
1412 let target_data = target_store.buffer.borrow();
1413
1414 check_rgb16!(target_data, image_width, 85);
1415 }
1416
1417 #[test]
1418 fn check_rgb16_resizing_vertical() {
1419 let image_width = 8;
1420 let image_height = 8;
1421 const CN: usize = 3;
1422 let mut image = vec![164; image_height * image_width * CN];
1423 for dst in image.chunks_exact_mut(3) {
1424 dst[0] = 124;
1425 dst[1] = 41;
1426 dst[2] = 99;
1427 }
1428 let scaler =
1429 Scaler::new(ResamplingFunction::Lanczos3).set_threading_policy(ThreadingPolicy::Single);
1430 let mut src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1431 src_store.bit_depth = 10;
1432 let mut target_store = ImageStoreMut::alloc_with_depth(image_width, image_height / 2, 16);
1433 let planned = scaler
1434 .plan_rgb_resampling16(src_store.size(), target_store.size(), 16)
1435 .unwrap();
1436 planned.resample(&src_store, &mut target_store).unwrap();
1437 let target_data = target_store.buffer.borrow();
1438
1439 check_rgb16!(target_data, image_width, 100);
1440 }
1441
1442 #[test]
1443 fn check_rgba16_resizing_vertical() {
1444 let image_width = 8;
1445 let image_height = 8;
1446 const CN: usize = 4;
1447 let mut image = vec![0u16; image_height * image_width * CN];
1448 for dst in image.chunks_exact_mut(4) {
1449 dst[0] = 124;
1450 dst[1] = 41;
1451 dst[2] = 99;
1452 dst[3] = 255;
1453 }
1454 let scaler =
1455 Scaler::new(ResamplingFunction::Lanczos3).set_threading_policy(ThreadingPolicy::Single);
1456 let mut src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1457 src_store.bit_depth = 10;
1458 let mut target_store = ImageStoreMut::alloc_with_depth(image_width, image_height / 2, 16);
1459 let planned = scaler
1460 .plan_rgba_resampling16(src_store.size(), target_store.size(), false, 16)
1461 .unwrap();
1462 planned.resample(&src_store, &mut target_store).unwrap();
1463 let target_data = target_store.buffer.borrow();
1464
1465 check_rgba8!(target_data, image_width, 180);
1466 }
1467
1468 #[test]
1469 fn check_rgba16_resizing_vertical_threading() {
1470 let image_width = 8;
1471 let image_height = 8;
1472 const CN: usize = 4;
1473 let mut image = vec![0u16; image_height * image_width * CN];
1474 for dst in image.chunks_exact_mut(4) {
1475 dst[0] = 124;
1476 dst[1] = 41;
1477 dst[2] = 99;
1478 dst[3] = 255;
1479 }
1480 let scaler = Scaler::new(ResamplingFunction::Lanczos3)
1481 .set_threading_policy(ThreadingPolicy::Adaptive);
1482 let mut src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1483 src_store.bit_depth = 10;
1484 let mut target_store = ImageStoreMut::alloc_with_depth(image_width, image_height / 2, 16);
1485 let planned = scaler
1486 .plan_rgba_resampling16(src_store.size(), target_store.size(), false, 16)
1487 .unwrap();
1488 planned.resample(&src_store, &mut target_store).unwrap();
1489 let target_data = target_store.buffer.borrow();
1490
1491 check_rgba8!(target_data, image_width, 180);
1492 }
1493
1494 #[test]
1495 fn check_rgba8_nearest_vertical() {
1496 let image_width = 255;
1497 let image_height = 512;
1498 const CN: usize = 4;
1499 let mut image = vec![0u8; image_height * image_width * CN];
1500 for dst in image.chunks_exact_mut(4) {
1501 dst[0] = 124;
1502 dst[1] = 41;
1503 dst[2] = 99;
1504 dst[3] = 77;
1505 }
1506 let mut scaler = Scaler::new(ResamplingFunction::Nearest);
1507 scaler.set_threading_policy(ThreadingPolicy::Single);
1508 let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1509 let mut target_store = ImageStoreMut::alloc(image_width, image_height / 2);
1510 let planned = scaler
1511 .plan_rgba_resampling(src_store.size(), target_store.size(), false)
1512 .unwrap();
1513 planned.resample(&src_store, &mut target_store).unwrap();
1514 let target_data = target_store.buffer.borrow();
1515
1516 check_rgba8!(target_data, image_width, 80);
1517 }
1518
1519 #[test]
1520 fn check_rgba8_nearest_vertical_threading() {
1521 let image_width = 255;
1522 let image_height = 512;
1523 const CN: usize = 4;
1524 let mut image = vec![0u8; image_height * image_width * CN];
1525 for dst in image.chunks_exact_mut(4) {
1526 dst[0] = 124;
1527 dst[1] = 41;
1528 dst[2] = 99;
1529 dst[3] = 77;
1530 }
1531 let scaler = Scaler::new(ResamplingFunction::Nearest)
1532 .set_threading_policy(ThreadingPolicy::Adaptive);
1533 let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1534 let mut target_store = ImageStoreMut::alloc(image_width, image_height / 2);
1535 let planned = scaler
1536 .plan_rgba_resampling(src_store.size(), target_store.size(), false)
1537 .unwrap();
1538 planned.resample(&src_store, &mut target_store).unwrap();
1539 let target_data = target_store.buffer.borrow();
1540
1541 check_rgba8!(target_data, image_width, 80);
1542 }
1543
1544 #[test]
1545 fn check_plane_s16_10bit_resizing_horizontal() {
1546 let image_width = 8;
1547 let image_height = 1;
1548 const CN: usize = 1;
1549 let mut image = vec![0i16; image_height * image_width * CN];
1550 for (i, px) in image.iter_mut().enumerate() {
1551 *px = (100 + i as i16 * 10).min(511);
1552 }
1553 image[0] = -200;
1554
1555 let scaler =
1556 Scaler::new(ResamplingFunction::Lanczos3).set_threading_policy(ThreadingPolicy::Single);
1557
1558 let src_store =
1559 ImageStore::<i16, CN>::from_slice(&image, image_width, image_height).unwrap();
1560 let mut target_store =
1561 ImageStoreMut::<i16, CN>::alloc_with_depth(image_width / 2, image_height, 10);
1562
1563 let planned = scaler
1564 .plan_planar_resampling_s16(src_store.size(), target_store.size(), 10)
1565 .unwrap();
1566 planned.resample(&src_store, &mut target_store).unwrap();
1567
1568 let target_data = target_store.buffer.borrow();
1569 for &px in target_data.iter() {
1571 assert!(
1572 px >= -512 && px <= 511,
1573 "pixel {px} out of 10-bit signed range"
1574 );
1575 }
1576 }
1577
1578 #[test]
1579 fn check_plane_s16_10bit_resizing_vertical() {
1580 let image_width = 8;
1581 let image_height = 8;
1582 const CN: usize = 1;
1583 let mut image = vec![0i16; image_height * image_width * CN];
1584 for px in image.iter_mut() {
1585 *px = 124;
1586 }
1587 image[0] = -200;
1588
1589 let scaler =
1590 Scaler::new(ResamplingFunction::Lanczos3).set_threading_policy(ThreadingPolicy::Single);
1591
1592 let src_store =
1593 ImageStore::<i16, CN>::from_slice(&image, image_width, image_height).unwrap();
1594 let mut target_store =
1595 ImageStoreMut::<i16, CN>::alloc_with_depth(image_width, image_height / 2, 10);
1596
1597 let planned = scaler
1598 .plan_planar_resampling_s16(src_store.size(), target_store.size(), 10)
1599 .unwrap();
1600 planned.resample(&src_store, &mut target_store).unwrap();
1601
1602 let target_data = target_store.buffer.borrow();
1603 for &px in target_data.iter() {
1604 assert!(
1605 px >= -512 && px <= 511,
1606 "pixel {px} out of 10-bit signed range"
1607 );
1608 }
1609 for &px in target_data.iter().skip(1) {
1610 assert!(
1611 (px - 124).abs() < 30,
1612 "flat region drifted: got {px}, expected ~124"
1613 );
1614 }
1615 }
1616
1617 #[test]
1618 fn check_plane_s16_16bit_resizing_horizontal() {
1619 let image_width = 8;
1620 let image_height = 1;
1621 const CN: usize = 1;
1622 let mut image = vec![0i16; image_height * image_width * CN];
1623 for (i, px) in image.iter_mut().enumerate() {
1624 *px = (1000 + i as i16 * 500).min(i16::MAX);
1625 }
1626 image[0] = i16::MIN;
1627
1628 let scaler =
1629 Scaler::new(ResamplingFunction::Lanczos3).set_threading_policy(ThreadingPolicy::Single);
1630
1631 let src_store =
1632 ImageStore::<i16, CN>::from_slice(&image, image_width, image_height).unwrap();
1633 let mut target_store =
1634 ImageStoreMut::<i16, CN>::alloc_with_depth(image_width / 2, image_height, 16);
1635
1636 let planned = scaler
1637 .plan_planar_resampling_s16(src_store.size(), target_store.size(), 16)
1638 .unwrap();
1639 planned.resample(&src_store, &mut target_store).unwrap();
1640
1641 let target_data = target_store.buffer.borrow();
1642 for &px in target_data.iter() {
1643 assert!(
1644 px >= i16::MIN && px <= i16::MAX,
1645 "pixel {px} out of 16-bit signed range"
1646 );
1647 }
1648 }
1649
1650 #[test]
1651 fn check_plane_s16_16bit_resizing_vertical() {
1652 let image_width = 8;
1653 let image_height = 8;
1654 const CN: usize = 1;
1655 let mut image = vec![0i16; image_height * image_width * CN];
1656 for px in image.iter_mut() {
1657 *px = 5000;
1658 }
1659
1660 let scaler =
1661 Scaler::new(ResamplingFunction::Lanczos3).set_threading_policy(ThreadingPolicy::Single);
1662
1663 let src_store =
1664 ImageStore::<i16, CN>::from_slice(&image, image_width, image_height).unwrap();
1665 let mut target_store =
1666 ImageStoreMut::<i16, CN>::alloc_with_depth(image_width, image_height / 2, 16);
1667
1668 let planned = scaler
1669 .plan_planar_resampling_s16(src_store.size(), target_store.size(), 16)
1670 .unwrap();
1671 planned.resample(&src_store, &mut target_store).unwrap();
1672
1673 let target_data = target_store.buffer.borrow();
1674 for &px in target_data.iter() {
1675 assert!(
1676 px >= i16::MIN && px <= i16::MAX,
1677 "pixel {px} out of 16-bit signed range"
1678 );
1679 }
1680 for &px in target_data.iter().skip(1) {
1682 assert!(
1683 (px as i32 - 5000).abs() < 500,
1684 "flat region drifted: got {px}, expected ~5000"
1685 );
1686 }
1687 }
1688
1689 #[test]
1690 fn check_plane_s16_10bit_both_axes() {
1691 let image_width = 8;
1692 let image_height = 8;
1693 const CN: usize = 1;
1694 let mut image = vec![0i16; image_height * image_width * CN];
1695 for px in image.iter_mut() {
1696 *px = 200;
1697 }
1698 image[0] = -300;
1699
1700 let scaler =
1701 Scaler::new(ResamplingFunction::Lanczos3).set_threading_policy(ThreadingPolicy::Single);
1702
1703 let src_store =
1704 ImageStore::<i16, CN>::from_slice(&image, image_width, image_height).unwrap();
1705 let mut target_store =
1706 ImageStoreMut::<i16, CN>::alloc_with_depth(image_width / 2, image_height / 2, 10);
1707
1708 let planned = scaler
1709 .plan_planar_resampling_s16(src_store.size(), target_store.size(), 10)
1710 .unwrap();
1711 planned.resample(&src_store, &mut target_store).unwrap();
1712
1713 let target_data = target_store.buffer.borrow();
1714 for &px in target_data.iter() {
1715 assert!(
1716 px >= -512 && px <= 511,
1717 "pixel {px} out of 10-bit signed range"
1718 );
1719 }
1720 }
1721
1722 #[test]
1723 fn check_plane_s16_16bit_both_axes() {
1724 let image_width = 8;
1725 let image_height = 8;
1726 const CN: usize = 1;
1727 let mut image = vec![0i16; image_height * image_width * CN];
1728 for px in image.iter_mut() {
1729 *px = 10000;
1730 }
1731 image[0] = i16::MIN;
1732
1733 let scaler =
1734 Scaler::new(ResamplingFunction::Lanczos3).set_threading_policy(ThreadingPolicy::Single);
1735
1736 let src_store =
1737 ImageStore::<i16, CN>::from_slice(&image, image_width, image_height).unwrap();
1738 let mut target_store =
1739 ImageStoreMut::<i16, CN>::alloc_with_depth(image_width / 2, image_height / 2, 16);
1740
1741 let planned = scaler
1742 .plan_planar_resampling_s16(src_store.size(), target_store.size(), 16)
1743 .unwrap();
1744 planned.resample(&src_store, &mut target_store).unwrap();
1745
1746 let target_data = target_store.buffer.borrow();
1747 for &px in target_data.iter() {
1748 assert!(
1749 px >= i16::MIN && px <= i16::MAX,
1750 "pixel {px} out of 16-bit signed range"
1751 );
1752 }
1753 }
1754}