Skip to main content

pic_scale/
scaler.rs

1/*
2 * Copyright (c) Radzivon Bartoshyk. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without modification,
5 * are permitted provided that the following conditions are met:
6 *
7 * 1.  Redistributions of source code must retain the above copyright notice, this
8 * list of conditions and the following disclaimer.
9 *
10 * 2.  Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 *
14 * 3.  Neither the name of the copyright holder nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29#![forbid(unsafe_code)]
30use crate::convolution::{ConvolutionOptions, HorizontalFilterPass, VerticalConvolutionPass};
31use crate::factory::{Ar30ByteOrder, Rgb30};
32use crate::image_size::ImageSize;
33use crate::image_store::{
34    AssociateAlpha, CheckStoreDensity, ImageStore, ImageStoreMut, UnassociateAlpha,
35};
36use crate::math::WeightsGenerator;
37use crate::plan::{
38    AlphaConvolvePlan, Ar30Destructuring, Ar30DestructuringImpl, Ar30Plan, NonAlphaConvolvePlan,
39    ResampleNearestPlan, Resampling, TrampolineFiltering,
40};
41use crate::threading_policy::ThreadingPolicy;
42use crate::validation::PicScaleError;
43use crate::{
44    CbCr8ImageStore, CbCr16ImageStore, CbCrF32ImageStore, Planar8ImageStore, Planar16ImageStore,
45    PlanarF32ImageStore, ResamplingFunction, ResamplingPlan, Rgb8ImageStore, Rgb16ImageStore,
46    RgbF32ImageStore, Rgba8ImageStore, Rgba16ImageStore, RgbaF32ImageStore,
47};
48use std::fmt::Debug;
49use std::marker::PhantomData;
50use std::sync::Arc;
51
52#[derive(Debug, Copy, Clone)]
53/// Represents base scaling structure
54pub struct Scaler {
55    pub(crate) function: ResamplingFunction,
56    pub(crate) threading_policy: ThreadingPolicy,
57    pub workload_strategy: WorkloadStrategy,
58}
59
60/// Defines execution hint about preferred strategy
61#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Default)]
62pub enum WorkloadStrategy {
63    /// Prefers quality to speed
64    PreferQuality,
65    /// Prefers speed to quality
66    #[default]
67    PreferSpeed,
68}
69
70impl Scaler {
71    /// Creates new [Scaler] instance with corresponding filter
72    ///
73    /// Creates default [crate::Scaler] with corresponding filter and default [ThreadingPolicy::Single]
74    ///
75    pub fn new(filter: ResamplingFunction) -> Self {
76        Scaler {
77            function: filter,
78            threading_policy: ThreadingPolicy::Single,
79            workload_strategy: WorkloadStrategy::default(),
80        }
81    }
82
83    /// Sets preferred workload strategy
84    ///
85    /// This is hint only, it may change something, or may not.
86    pub fn set_workload_strategy(&mut self, workload_strategy: WorkloadStrategy) -> Self {
87        self.workload_strategy = workload_strategy;
88        *self
89    }
90}
91
92impl Scaler {
93    pub(crate) fn plan_generic_resize<
94        T: Clone + Copy + Debug + Send + Sync + Default + WeightsGenerator<W> + 'static,
95        W,
96        const N: usize,
97    >(
98        &self,
99        source_size: ImageSize,
100        destination_size: ImageSize,
101        bit_depth: usize,
102    ) -> Result<Arc<Resampling<T, N>>, PicScaleError>
103    where
104        for<'a> ImageStore<'a, T, N>:
105            VerticalConvolutionPass<T, W, N> + HorizontalFilterPass<T, W, N>,
106        for<'a> ImageStoreMut<'a, T, N>: CheckStoreDensity,
107    {
108        if self.function == ResamplingFunction::Nearest {
109            return Ok(Arc::new(ResampleNearestPlan {
110                source_size,
111                target_size: destination_size,
112                threading_policy: self.threading_policy,
113                _phantom_data: PhantomData,
114            }));
115        }
116        let vertical_filters =
117            T::make_weights(self.function, source_size.height, destination_size.height)?;
118        let horizontal_filters =
119            T::make_weights(self.function, source_size.width, destination_size.width)?;
120        let options = ConvolutionOptions {
121            workload_strategy: self.workload_strategy,
122            bit_depth,
123            src_size: source_size,
124            dst_size: destination_size,
125        };
126        let vertical_plan =
127            ImageStore::<T, N>::vertical_plan(vertical_filters, self.threading_policy, options);
128        let horizontal_plan =
129            ImageStore::<T, N>::horizontal_plan(horizontal_filters, self.threading_policy, options);
130
131        let should_do_horizontal = source_size.width != destination_size.width;
132        let should_do_vertical = source_size.height != destination_size.height;
133
134        let trampoline_filter = Arc::new(TrampolineFiltering {
135            horizontal_filter: horizontal_plan.clone(),
136            vertical_filter: vertical_plan.clone(),
137            source_size,
138            target_size: destination_size,
139        });
140
141        Ok(Arc::new(NonAlphaConvolvePlan {
142            source_size,
143            target_size: destination_size,
144            horizontal_filter: horizontal_plan,
145            vertical_filter: vertical_plan,
146            trampoline_filter,
147            should_do_vertical,
148            should_do_horizontal,
149            threading_policy: self.threading_policy,
150        }))
151    }
152
153    pub(crate) fn plan_generic_resize_with_alpha<
154        T: Clone + Copy + Debug + Send + Sync + Default + WeightsGenerator<W> + 'static,
155        W,
156        const N: usize,
157    >(
158        &self,
159        source_size: ImageSize,
160        destination_size: ImageSize,
161        bit_depth: usize,
162        needs_alpha_premultiplication: bool,
163    ) -> Result<Arc<Resampling<T, N>>, PicScaleError>
164    where
165        for<'a> ImageStore<'a, T, N>:
166            VerticalConvolutionPass<T, W, N> + HorizontalFilterPass<T, W, N> + AssociateAlpha<T, N>,
167        for<'a> ImageStoreMut<'a, T, N>: CheckStoreDensity + UnassociateAlpha<T, N>,
168    {
169        if self.function == ResamplingFunction::Nearest {
170            return Ok(Arc::new(ResampleNearestPlan {
171                source_size,
172                target_size: destination_size,
173                threading_policy: self.threading_policy,
174                _phantom_data: PhantomData,
175            }));
176        }
177        if !needs_alpha_premultiplication {
178            return self.plan_generic_resize(source_size, destination_size, bit_depth);
179        }
180        let vertical_filters =
181            T::make_weights(self.function, source_size.height, destination_size.height)?;
182        let horizontal_filters =
183            T::make_weights(self.function, source_size.width, destination_size.width)?;
184        let options = ConvolutionOptions {
185            workload_strategy: self.workload_strategy,
186            bit_depth,
187            src_size: source_size,
188            dst_size: destination_size,
189        };
190        let vertical_plan =
191            ImageStore::<T, N>::vertical_plan(vertical_filters, self.threading_policy, options);
192        let horizontal_plan =
193            ImageStore::<T, N>::horizontal_plan(horizontal_filters, self.threading_policy, options);
194
195        let should_do_horizontal = source_size.width != destination_size.width;
196        let should_do_vertical = source_size.height != destination_size.height;
197
198        let trampoline_filter = Arc::new(TrampolineFiltering {
199            horizontal_filter: horizontal_plan.clone(),
200            vertical_filter: vertical_plan.clone(),
201            source_size,
202            target_size: destination_size,
203        });
204
205        Ok(Arc::new(AlphaConvolvePlan {
206            source_size,
207            target_size: destination_size,
208            threading_policy: self.threading_policy,
209            horizontal_filter: horizontal_plan,
210            vertical_filter: vertical_plan,
211            trampoline_filter,
212            should_do_vertical,
213            should_do_horizontal,
214            workload_strategy: self.workload_strategy,
215        }))
216    }
217
218    /// Creates a resampling plan for a single-channel (planar/grayscale) `u8` image.
219    ///
220    /// The returned [`Arc<Resampling<u8, 1>>`] can be executed repeatedly against images
221    /// of `source_size` to produce output of `target_size` without recomputing filter weights.
222    ///
223    /// # Arguments
224    ///
225    /// - `source_size` — Dimensions of the input image.
226    /// - `target_size` — Desired dimensions of the output image.
227    /// # Example
228    ///
229    /// ```rust,no_run,ignore
230    /// let plan = scaler.plan_planar_resampling(source_size, target_size)?;
231    /// plan.resample(&store, &mut target_store)?;
232    /// ```
233    pub fn plan_planar_resampling(
234        &self,
235        source_size: ImageSize,
236        target_size: ImageSize,
237    ) -> Result<Arc<Resampling<u8, 1>>, PicScaleError> {
238        self.plan_generic_resize(source_size, target_size, 8)
239    }
240
241    /// Creates a resampling plan for a two-channel grayscale + alpha (`GA`) `u8` image.
242    ///
243    /// When `premultiply_alpha` is `true` the alpha channel is pre-multiplied into the gray
244    /// channel before resampling and un-multiplied afterward.
245    ///
246    /// # Arguments
247    ///
248    /// - `source_size` — Dimensions of the input image.
249    /// - `target_size` — Desired dimensions of the output image.
250    /// - `premultiply_alpha` — Whether to premultiply alpha before resampling.
251    ///
252    /// # Example
253    ///
254    /// ```rust,no_run,ignore
255    /// // Resample with alpha-aware filtering to avoid dark fringing
256    /// let plan = scaler.plan_gray_alpha_resampling(source_size, target_size, true)?;
257    /// plan.resample(&store, &mut target_store)?;
258    /// ```
259    pub fn plan_gray_alpha_resampling(
260        &self,
261        source_size: ImageSize,
262        target_size: ImageSize,
263        premultiply_alpha: bool,
264    ) -> Result<Arc<Resampling<u8, 2>>, PicScaleError> {
265        if premultiply_alpha {
266            self.plan_generic_resize_with_alpha(source_size, target_size, 8, premultiply_alpha)
267        } else {
268            self.plan_generic_resize(source_size, target_size, 8)
269        }
270    }
271
272    /// Creates a resampling plan for a two-channel chroma (`CbCr`) `u8` image.
273    ///
274    /// Intended for the chroma planes of YCbCr images (e.g. the `Cb`/`Cr` planes in
275    /// 4:2:0 or 4:2:2 video), where both channels are treated as independent signals
276    /// with no alpha relationship. For the luma plane use [`plan_planar_resampling`].
277    ///
278    /// # Arguments
279    ///
280    /// - `source_size` — Dimensions of the input chroma plane.
281    /// - `target_size` — Desired dimensions of the output chroma plane.
282    ///
283    /// # Example
284    ///
285    /// ```rust,no_run,ignore
286    /// let plan = scaler.plan_cbcr_resampling(source_size, target_size)?;
287    /// plan.resample(&cbcr_store, &mut target_cbcr_store)?;
288    /// ```
289    pub fn plan_cbcr_resampling(
290        &self,
291        source_size: ImageSize,
292        target_size: ImageSize,
293    ) -> Result<Arc<Resampling<u8, 2>>, PicScaleError> {
294        self.plan_generic_resize(source_size, target_size, 8)
295    }
296
297    /// Creates a resampling plan for a three-channel RGB `u8` image.
298    ///
299    /// The returned [`Arc<Resampling<u8, 3>>`] encodes all filter weights for scaling
300    /// from `source_size` to `target_size` and can be reused across many frames without
301    /// recomputation.
302    ///
303    /// # Arguments
304    ///
305    /// - `source_size` — Dimensions of the input image.
306    /// - `target_size` — Desired dimensions of the output image.
307    ///
308    /// # Example
309    ///
310    /// ```rust,no_run,ignore
311    /// let plan = scaler.plan_rgb_resampling(source_size, target_size)?;
312    /// plan.resample(&store, &mut target_store)?;
313    /// ```
314    pub fn plan_rgb_resampling(
315        &self,
316        source_size: ImageSize,
317        target_size: ImageSize,
318    ) -> Result<Arc<Resampling<u8, 3>>, PicScaleError> {
319        self.plan_generic_resize(source_size, target_size, 8)
320    }
321
322    /// Creates a resampling plan for a four-channel RGBA `u8` image.
323    ///
324    /// When `premultiply_alpha` is `true` the RGB channels are pre-multiplied by alpha
325    /// before resampling and un-multiplied afterward.
326    ///
327    /// # Arguments
328    ///
329    /// - `source_size` — Dimensions of the input image.
330    /// - `target_size` — Desired dimensions of the output image.
331    /// - `premultiply_alpha` — Whether to premultiply alpha before resampling.
332    ///
333    /// # Example
334    ///
335    /// ```rust,no_run,ignore
336    /// // Resample a sprite sheet with correct alpha blending
337    /// let plan = scaler.plan_rgba_resampling(source_size, target_size, true)?;
338    /// plan.resample(&store, &mut target_store)?;
339    /// ```
340    pub fn plan_rgba_resampling(
341        &self,
342        source_size: ImageSize,
343        target_size: ImageSize,
344        premultiply_alpha: bool,
345    ) -> Result<Arc<Resampling<u8, 4>>, PicScaleError> {
346        if premultiply_alpha {
347            self.plan_generic_resize_with_alpha(source_size, target_size, 8, premultiply_alpha)
348        } else {
349            self.plan_generic_resize(source_size, target_size, 8)
350        }
351    }
352
353    /// Creates a resampling plan for a single-channel (planar/grayscale) `u16` image.
354    ///
355    /// The 16-bit variant of [`plan_planar_resampling`], suitable for high-bit-depth
356    /// grayscale content such as HDR images or luma planes from 10/12-bit video.
357    ///
358    /// # Arguments
359    ///
360    /// - `source_size` — Dimensions of the input image.
361    /// - `target_size` — Desired dimensions of the output image.
362    /// - `bit_depth` — Effective bit depth of the pixel data (e.g. `10`, `12`, or `16`).
363    ///   Must not exceed `16`.
364    ///
365    /// # Example
366    ///
367    /// ```rust,no_run,ignore
368    /// let plan = scaler.plan_planar_resampling16(source_size, target_size, 12)?;
369    /// plan.resample(&store, &mut target_store)?;
370    /// ```
371    pub fn plan_planar_resampling16(
372        &self,
373        source_size: ImageSize,
374        target_size: ImageSize,
375        bit_depth: usize,
376    ) -> Result<Arc<Resampling<u16, 1>>, PicScaleError> {
377        self.plan_generic_resize(source_size, target_size, bit_depth)
378    }
379
380    /// Creates a resampling plan for a single-channel (planar/grayscale) `i16` image.
381    ///
382    /// The 16-bit variant of [`plan_planar_resampling`], suitable for high-bit-depth
383    /// grayscale content such as HDR images or luma planes from 10/12-bit video.
384    ///
385    /// # Arguments
386    ///
387    /// - `source_size` — Dimensions of the input image.
388    /// - `target_size` — Desired dimensions of the output image.
389    /// - `bit_depth` — Effective bit depth of the pixel data (e.g. `10`, `12`, or `16`).
390    ///   Must not exceed `16`.
391    ///
392    /// # Example
393    ///
394    /// ```rust,no_run,ignore
395    /// let plan = scaler.plan_planar_resampling_s16(source_size, target_size, 12)?;
396    /// plan.resample(&store, &mut target_store)?;
397    /// ```
398    pub fn plan_planar_resampling_s16(
399        &self,
400        source_size: ImageSize,
401        target_size: ImageSize,
402        bit_depth: usize,
403    ) -> Result<Arc<Resampling<i16, 1>>, PicScaleError> {
404        self.plan_generic_resize(source_size, target_size, bit_depth)
405    }
406
407    /// Creates a resampling plan for a two-channel chroma (`CbCr`) `u16` image.
408    ///
409    /// The 16-bit variant of [`plan_cbcr_resampling`], intended for high-bit-depth chroma
410    /// planes of YCbCr content (e.g. 10-bit 4:2:0 or 4:2:2 video). Both channels are
411    /// treated as independent signals with no alpha relationship.
412    ///
413    /// # Arguments
414    ///
415    /// - `source_size` — Dimensions of the input chroma plane.
416    /// - `target_size` — Desired dimensions of the output chroma plane.
417    /// - `bit_depth` — Effective bit depth of the pixel data (e.g. `10`, `12`, or `16`).
418    ///   Must not exceed `16`.
419    ///
420    /// # Example
421    ///
422    /// ```rust,no_run,ignore
423    /// let plan = scaler.plan_cbcr_resampling16(source_size, target_size, 10)?;
424    /// plan.resample(&cbcr_store, &mut target_cbcr_store)?;
425    /// ```
426    pub fn plan_cbcr_resampling16(
427        &self,
428        source_size: ImageSize,
429        target_size: ImageSize,
430        bit_depth: usize,
431    ) -> Result<Arc<Resampling<u16, 2>>, PicScaleError> {
432        self.plan_generic_resize(source_size, target_size, bit_depth)
433    }
434
435    /// Creates a resampling plan for a two-channel grayscale + alpha (`GA`) `u16` image.
436    ///
437    /// The 16-bit variant of [`plan_gray_alpha_resampling`]. When `premultiply_alpha` is
438    /// `true` the gray channel is pre-multiplied by alpha before resampling and
439    /// un-multiplied afterward.
440    ///
441    /// # Arguments
442    ///
443    /// - `source_size` — Dimensions of the input image.
444    /// - `target_size` — Desired dimensions of the output image.
445    /// - `premultiply_alpha` — Whether to premultiply alpha before resampling.
446    /// - `bit_depth` — Effective bit depth of the pixel data (e.g. `10`, `12`, or `16`).
447    ///   Must not exceed `16`.
448    ///
449    /// # Example
450    ///
451    /// ```rust,no_run,ignore
452    /// let plan = scaler.plan_gray_alpha_resampling16(source_size, target_size, true, 16)?;
453    /// plan.resample(&store, &mut target_store)?;
454    /// ```
455    pub fn plan_gray_alpha_resampling16(
456        &self,
457        source_size: ImageSize,
458        target_size: ImageSize,
459        premultiply_alpha: bool,
460        bit_depth: usize,
461    ) -> Result<Arc<Resampling<u16, 2>>, PicScaleError> {
462        if premultiply_alpha {
463            self.plan_generic_resize_with_alpha(
464                source_size,
465                target_size,
466                bit_depth,
467                premultiply_alpha,
468            )
469        } else {
470            self.plan_cbcr_resampling16(source_size, target_size, bit_depth)
471        }
472    }
473
474    /// Creates a resampling plan for a three-channel RGB `u16` image.
475    ///
476    /// The 16-bit variant of [`plan_rgb_resampling`], suitable for high-bit-depth color
477    /// images such as 10/12-bit HDR or wide-gamut content. All three channels are
478    /// resampled independently with no alpha relationship.
479    ///
480    /// # Arguments
481    ///
482    /// - `source_size` — Dimensions of the input image.
483    /// - `target_size` — Desired dimensions of the output image.
484    /// - `bit_depth` — Effective bit depth of the pixel data (e.g. `10`, `12`, or `16`).
485    ///   Must not exceed `16`.
486    ///
487    /// # Example
488    ///
489    /// ```rust,no_run,ignore
490    /// let plan = scaler.plan_rgb_resampling16(source_size, target_size, 12)?;
491    /// plan.resample(&store, &mut target_store)?;
492    /// ```
493    pub fn plan_rgb_resampling16(
494        &self,
495        source_size: ImageSize,
496        target_size: ImageSize,
497        bit_depth: usize,
498    ) -> Result<Arc<Resampling<u16, 3>>, PicScaleError> {
499        self.plan_generic_resize(source_size, target_size, bit_depth)
500    }
501
502    /// Creates a resampling plan for a four-channel RGBA `u16` image.
503    ///
504    /// The 16-bit variant of [`plan_rgba_resampling`]. When `premultiply_alpha` is `true`
505    /// the RGB channels are pre-multiplied by alpha before resampling and un-multiplied
506    /// afterward.
507    ///
508    /// # Arguments
509    ///
510    /// - `source_size` — Dimensions of the input image.
511    /// - `target_size` — Desired dimensions of the output image.
512    /// - `premultiply_alpha` — Whether to premultiply alpha before resampling.
513    /// - `bit_depth` — Effective bit depth of the pixel data (e.g. `10`, `12`, or `16`).
514    ///   Must not exceed `16`.
515    ///
516    /// # Example
517    ///
518    /// ```rust,no_run,ignore
519    /// let plan = scaler.plan_rgba_resampling16(source_size, target_size, true, 10)?;
520    /// plan.resample(&store, &mut target_store)?;
521    /// ```
522    pub fn plan_rgba_resampling16(
523        &self,
524        source_size: ImageSize,
525        target_size: ImageSize,
526        premultiply_alpha: bool,
527        bit_depth: usize,
528    ) -> Result<Arc<Resampling<u16, 4>>, PicScaleError> {
529        if premultiply_alpha {
530            self.plan_generic_resize_with_alpha(
531                source_size,
532                target_size,
533                bit_depth,
534                premultiply_alpha,
535            )
536        } else {
537            self.plan_generic_resize(source_size, target_size, bit_depth)
538        }
539    }
540
541    /// Creates a resampling plan for a single-channel (planar/grayscale) `f32` image.
542    ///
543    /// The `f32` variant of [`plan_planar_resampling`], suitable for HDR or linear-light
544    /// grayscale content where full floating-point precision is required.
545    ///
546    /// The internal accumulator precision is selected automatically based on the scaler's
547    /// [`WorkloadStrategy`]:
548    /// - [`PreferQuality`](WorkloadStrategy::PreferQuality) — accumulates in `f64` for
549    ///   maximum numerical accuracy.
550    /// - [`PreferSpeed`](WorkloadStrategy::PreferSpeed) — accumulates in `f32` for
551    ///   faster throughput at a small precision cost.
552    ///
553    /// # Arguments
554    ///
555    /// - `source_size` — Dimensions of the input image.
556    /// - `target_size` — Desired dimensions of the output image.
557    ///
558    /// # Example
559    ///
560    /// ```rust,no_run,ignore
561    /// let plan = scaler.plan_planar_resampling_f32(source_size, target_size)?;
562    /// plan.resample(&store, &mut target_store)?;
563    /// ```
564    pub fn plan_planar_resampling_f32(
565        &self,
566        source_size: ImageSize,
567        target_size: ImageSize,
568    ) -> Result<Arc<Resampling<f32, 1>>, PicScaleError> {
569        match self.workload_strategy {
570            WorkloadStrategy::PreferQuality => {
571                self.plan_generic_resize::<f32, f64, 1>(source_size, target_size, 8)
572            }
573            WorkloadStrategy::PreferSpeed => {
574                self.plan_generic_resize::<f32, f32, 1>(source_size, target_size, 8)
575            }
576        }
577    }
578
579    /// Creates a resampling plan for a two-channel chroma (`CbCr`) `f32` image.
580    ///
581    /// The `f32` variant of [`plan_cbcr_resampling`], intended for floating-point chroma
582    /// planes of YCbCr content. Both channels are treated as independent signals with no
583    /// alpha relationship.
584    ///
585    /// The internal accumulator precision is selected automatically based on the scaler's
586    /// [`WorkloadStrategy`]:
587    /// - [`PreferQuality`](WorkloadStrategy::PreferQuality) — accumulates in `f64` for
588    ///   maximum numerical accuracy.
589    /// - [`PreferSpeed`](WorkloadStrategy::PreferSpeed) — accumulates in `f32` for
590    ///   faster throughput at a small precision cost.
591    ///
592    /// # Arguments
593    ///
594    /// - `source_size` — Dimensions of the input chroma plane.
595    /// - `target_size` — Desired dimensions of the output chroma plane.
596    ///
597    /// # Example
598    ///
599    /// ```rust,no_run,ignore
600    /// let plan = scaler.plan_cbcr_resampling_f32(source_size, target_size)?;
601    /// plan.resample(&cbcr_store, &mut target_cbcr_store)?;
602    /// ```
603    pub fn plan_cbcr_resampling_f32(
604        &self,
605        source_size: ImageSize,
606        target_size: ImageSize,
607    ) -> Result<Arc<dyn ResamplingPlan<f32, 2> + Send + Sync>, PicScaleError> {
608        match self.workload_strategy {
609            WorkloadStrategy::PreferQuality => {
610                self.plan_generic_resize::<f32, f64, 2>(source_size, target_size, 8)
611            }
612            WorkloadStrategy::PreferSpeed => {
613                self.plan_generic_resize::<f32, f32, 2>(source_size, target_size, 8)
614            }
615        }
616    }
617
618    /// Creates a resampling plan for a two-channel grayscale + alpha (`GA`) `f32` image.
619    ///
620    /// The `f32` variant of [`plan_gray_alpha_resampling`]. When `premultiply_alpha` is
621    /// `true` the gray channel is pre-multiplied by alpha before resampling and
622    /// un-multiplied afterward, preventing dark fringing around transparent edges.
623    /// Set it to `false` if the image uses straight alpha or the channels should be
624    /// filtered independently.
625    ///
626    /// The internal accumulator precision is selected automatically based on the scaler's
627    /// [`WorkloadStrategy`]:
628    /// - [`PreferQuality`](WorkloadStrategy::PreferQuality) — accumulates in `f64` for
629    ///   maximum numerical accuracy.
630    /// - [`PreferSpeed`](WorkloadStrategy::PreferSpeed) — accumulates in `f32` for
631    ///   faster throughput at a small precision cost.
632    ///
633    /// # Arguments
634    ///
635    /// - `source_size` — Dimensions of the input image.
636    /// - `target_size` — Desired dimensions of the output image.
637    /// - `premultiply_alpha` — Whether to premultiply alpha before resampling.
638    ///
639    /// # Example
640    ///
641    /// ```rust,no_run,ignore
642    /// let plan = scaler.plan_gray_alpha_resampling_f32(source_size, target_size, true)?;
643    /// plan.resample(&store, &mut target_store)?;
644    /// ```
645    pub fn plan_gray_alpha_resampling_f32(
646        &self,
647        source_size: ImageSize,
648        target_size: ImageSize,
649        premultiply_alpha: bool,
650    ) -> Result<Arc<Resampling<f32, 2>>, PicScaleError> {
651        if premultiply_alpha {
652            match self.workload_strategy {
653                WorkloadStrategy::PreferQuality => self
654                    .plan_generic_resize_with_alpha::<f32, f64, 2>(
655                        source_size,
656                        target_size,
657                        8,
658                        premultiply_alpha,
659                    ),
660                WorkloadStrategy::PreferSpeed => self
661                    .plan_generic_resize_with_alpha::<f32, f32, 2>(
662                        source_size,
663                        target_size,
664                        8,
665                        premultiply_alpha,
666                    ),
667            }
668        } else {
669            self.plan_cbcr_resampling_f32(source_size, target_size)
670        }
671    }
672
673    /// Creates a resampling plan for a three-channel RGB `f32` image.
674    ///
675    /// The `f32` variant of [`plan_rgb_resampling`], suitable for HDR or linear-light
676    /// color images where full floating-point precision is required. All three channels
677    /// are resampled independently with no alpha relationship.
678    ///
679    /// The internal accumulator precision is selected automatically based on the scaler's
680    /// [`WorkloadStrategy`]:
681    /// - [`PreferQuality`](WorkloadStrategy::PreferQuality) — accumulates in `f64` for
682    ///   maximum numerical accuracy.
683    /// - [`PreferSpeed`](WorkloadStrategy::PreferSpeed) — accumulates in `f32` for
684    ///   faster throughput at a small precision cost.
685    ///
686    /// # Arguments
687    ///
688    /// - `source_size` — Dimensions of the input image.
689    /// - `target_size` — Desired dimensions of the output image.
690    ///
691    /// # Example
692    ///
693    /// ```rust,no_run,ignore
694    /// let plan = scaler.plan_rgb_resampling_f32(source_size, target_size)?;
695    /// plan.resample(&store, &mut target_store)?;
696    /// ```
697    pub fn plan_rgb_resampling_f32(
698        &self,
699        source_size: ImageSize,
700        target_size: ImageSize,
701    ) -> Result<Arc<Resampling<f32, 3>>, PicScaleError> {
702        match self.workload_strategy {
703            WorkloadStrategy::PreferQuality => {
704                self.plan_generic_resize::<f32, f64, 3>(source_size, target_size, 8)
705            }
706            WorkloadStrategy::PreferSpeed => {
707                self.plan_generic_resize::<f32, f32, 3>(source_size, target_size, 8)
708            }
709        }
710    }
711
712    /// Creates a resampling plan for a four-channel RGBA `f32` image.
713    ///
714    /// The `f32` variant of [`plan_rgba_resampling`]. When `premultiply_alpha` is `true`
715    /// the RGB channels are pre-multiplied by alpha before resampling and un-multiplied
716    /// afterward, preventing dark halos around semi-transparent edges. Set it to `false`
717    /// if the image uses straight alpha or the channels should be filtered independently.
718    ///
719    /// The internal accumulator precision is selected automatically based on the scaler's
720    /// [`WorkloadStrategy`]:
721    /// - [`PreferQuality`](WorkloadStrategy::PreferQuality) — accumulates in `f64` for
722    ///   maximum numerical accuracy.
723    /// - [`PreferSpeed`](WorkloadStrategy::PreferSpeed) — accumulates in `f32` for
724    ///   faster throughput at a small precision cost.
725    ///
726    /// # Arguments
727    ///
728    /// - `source_size` — Dimensions of the input image.
729    /// - `target_size` — Desired dimensions of the output image.
730    /// - `premultiply_alpha` — Whether to premultiply alpha before resampling.
731    ///
732    /// # Example
733    ///
734    /// ```rust,no_run,ignore
735    /// let plan = scaler.plan_rgba_resampling_f32(source_size, target_size, true)?;
736    /// plan.resample(&store, &mut target_store)?;
737    /// ```
738    pub fn plan_rgba_resampling_f32(
739        &self,
740        source_size: ImageSize,
741        target_size: ImageSize,
742        premultiply_alpha: bool,
743    ) -> Result<Arc<Resampling<f32, 4>>, PicScaleError> {
744        if premultiply_alpha {
745            match self.workload_strategy {
746                WorkloadStrategy::PreferQuality => self
747                    .plan_generic_resize_with_alpha::<f32, f64, 4>(
748                        source_size,
749                        target_size,
750                        8,
751                        premultiply_alpha,
752                    ),
753                WorkloadStrategy::PreferSpeed => self
754                    .plan_generic_resize_with_alpha::<f32, f32, 4>(
755                        source_size,
756                        target_size,
757                        8,
758                        premultiply_alpha,
759                    ),
760            }
761        } else {
762            match self.workload_strategy {
763                WorkloadStrategy::PreferQuality => {
764                    self.plan_generic_resize::<f32, f64, 4>(source_size, target_size, 8)
765                }
766                WorkloadStrategy::PreferSpeed => {
767                    self.plan_generic_resize::<f32, f32, 4>(source_size, target_size, 8)
768                }
769            }
770        }
771    }
772
773    pub fn set_threading_policy(&mut self, threading_policy: ThreadingPolicy) -> Self {
774        self.threading_policy = threading_policy;
775        *self
776    }
777}
778
779impl Scaler {
780    pub(crate) fn plan_resize_ar30<const AR30_ORDER: usize>(
781        &self,
782        ar30_type: Rgb30,
783        source_size: ImageSize,
784        destination_size: ImageSize,
785    ) -> Result<Arc<Resampling<u8, 4>>, PicScaleError> {
786        if self.function == ResamplingFunction::Nearest {
787            return Ok(Arc::new(ResampleNearestPlan {
788                source_size,
789                target_size: destination_size,
790                threading_policy: self.threading_policy,
791                _phantom_data: PhantomData,
792            }));
793        }
794        let inner_plan = self.plan_rgb_resampling16(source_size, destination_size, 10)?;
795        let mut _decomposer: Arc<dyn Ar30Destructuring + Send + Sync> =
796            Arc::new(Ar30DestructuringImpl::<AR30_ORDER> { rgb30: ar30_type });
797        #[cfg(all(target_arch = "x86_64", feature = "avx"))]
798        {
799            if std::arch::is_x86_feature_detected!("avx2") {
800                let vertical_filters =
801                    u8::make_weights(self.function, source_size.height, destination_size.height)?;
802                let horizontal_filters =
803                    u8::make_weights(self.function, source_size.width, destination_size.width)?;
804                use crate::avx2::{
805                    avx_column_handler_fixed_point_ar30, avx_convolve_horizontal_rgba_rows_4_ar30,
806                    avx_convolve_horizontal_rgba_rows_ar30,
807                };
808                use crate::plan::{HorizontalFiltering, VerticalFiltering};
809                let vertical_plan = Arc::new(VerticalFiltering {
810                    filter_row: match ar30_type {
811                        Rgb30::Ar30 => {
812                            avx_column_handler_fixed_point_ar30::<
813                                { Rgb30::Ar30 as usize },
814                                AR30_ORDER,
815                            >
816                        }
817                        Rgb30::Ra30 => {
818                            avx_column_handler_fixed_point_ar30::<
819                                { Rgb30::Ra30 as usize },
820                                AR30_ORDER,
821                            >
822                        }
823                    },
824                    filter_weights: vertical_filters
825                        .numerical_approximation_i16::<{ crate::support::PRECISION }>(0),
826                    threading_policy: self.threading_policy,
827                });
828                let horizontal_plan = Arc::new(HorizontalFiltering {
829                    filter_row: match ar30_type {
830                        Rgb30::Ar30 => {
831                            avx_convolve_horizontal_rgba_rows_ar30::<
832                                { Rgb30::Ar30 as usize },
833                                AR30_ORDER,
834                            >
835                        }
836                        Rgb30::Ra30 => {
837                            avx_convolve_horizontal_rgba_rows_ar30::<
838                                { Rgb30::Ra30 as usize },
839                                AR30_ORDER,
840                            >
841                        }
842                    },
843                    filter_4_rows: Some(match ar30_type {
844                        Rgb30::Ar30 => {
845                            avx_convolve_horizontal_rgba_rows_4_ar30::<
846                                { Rgb30::Ar30 as usize },
847                                AR30_ORDER,
848                            >
849                        }
850                        Rgb30::Ra30 => {
851                            avx_convolve_horizontal_rgba_rows_4_ar30::<
852                                { Rgb30::Ra30 as usize },
853                                AR30_ORDER,
854                            >
855                        }
856                    }),
857                    threading_policy: self.threading_policy,
858                    filter_weights: horizontal_filters
859                        .numerical_approximation_i16::<{ crate::support::PRECISION }>(0),
860                });
861
862                let should_do_horizontal = source_size.width != destination_size.width;
863                let should_do_vertical = source_size.height != destination_size.height;
864
865                let trampoline_filter = Arc::new(TrampolineFiltering {
866                    horizontal_filter: horizontal_plan.clone(),
867                    vertical_filter: vertical_plan.clone(),
868                    source_size,
869                    target_size: destination_size,
870                });
871
872                return Ok(Arc::new(NonAlphaConvolvePlan {
873                    source_size,
874                    target_size: destination_size,
875                    horizontal_filter: horizontal_plan,
876                    vertical_filter: vertical_plan,
877                    trampoline_filter,
878                    should_do_vertical,
879                    should_do_horizontal,
880                    threading_policy: self.threading_policy,
881                }));
882            }
883        }
884        Ok(Arc::new(Ar30Plan {
885            source_size,
886            target_size: destination_size,
887            inner_filter: inner_plan,
888            decomposer: _decomposer,
889        }))
890    }
891
892    /// Creates a resampling plan for an AR30 (`RGBA2101010`) packed 10-bit image.
893    ///
894    /// AR30 stores each pixel as a 32-bit word with 10 bits per RGB channel and a
895    /// 2-bit alpha.
896    ///
897    /// The `order` argument controls the byte layout of the packed word:
898    /// - [`Ar30ByteOrder::Host`] — native endianness of the current platform.
899    /// - [`Ar30ByteOrder::Network`] — big-endian (network) byte order.
900    ///
901    /// # Arguments
902    ///
903    /// - `source_size` — Dimensions of the input image.
904    /// - `target_size` — Desired dimensions of the output image.
905    /// - `order` — Byte order of the packed AR30 words.
906    ///
907    /// # Example
908    ///
909    /// ```rust,no_run,ignore
910    /// let plan = scaler.plan_ar30_resampling(source_size, target_size, Ar30ByteOrder::Host)?;
911    /// plan.resample(&store, &mut target_store)?;
912    /// ```
913    pub fn plan_ar30_resampling(
914        &self,
915        source_size: ImageSize,
916        target_size: ImageSize,
917        order: Ar30ByteOrder,
918    ) -> Result<Arc<Resampling<u8, 4>>, PicScaleError> {
919        match order {
920            Ar30ByteOrder::Host => self.plan_resize_ar30::<{ Ar30ByteOrder::Host as usize }>(
921                Rgb30::Ar30,
922                source_size,
923                target_size,
924            ),
925            Ar30ByteOrder::Network => self.plan_resize_ar30::<{ Ar30ByteOrder::Network as usize }>(
926                Rgb30::Ar30,
927                source_size,
928                target_size,
929            ),
930        }
931    }
932
933    /// Creates a resampling plan for an RA30 (`RGBA1010102`) packed 10-bit image.
934    ///
935    /// RA30 stores each pixel as a 32-bit word with 10 bits per RGB channel and a
936    /// 2-bit alpha in the least-significant position.
937    ///
938    /// The `order` argument controls the byte layout of the packed word:
939    /// - [`Ar30ByteOrder::Host`] — native endianness of the current platform.
940    /// - [`Ar30ByteOrder::Network`] — big-endian (network) byte order.
941    ///
942    /// # Arguments
943    ///
944    /// - `source_size` — Dimensions of the input image.
945    /// - `target_size` — Desired dimensions of the output image.
946    /// - `order` — Byte order of the packed RA30 words.
947    ///
948    /// # Example
949    ///
950    /// ```rust,no_run,ignore
951    /// let plan = scaler.resize_ra30(source_size, target_size, Ar30ByteOrder::Host)?;
952    /// plan.resample(&store, &mut target_store)?;
953    /// ```
954    pub fn plan_ra30_resampling(
955        &self,
956        source_size: ImageSize,
957        target_size: ImageSize,
958        order: Ar30ByteOrder,
959    ) -> Result<Arc<Resampling<u8, 4>>, PicScaleError> {
960        match order {
961            Ar30ByteOrder::Host => self.plan_resize_ar30::<{ Ar30ByteOrder::Host as usize }>(
962                Rgb30::Ra30,
963                source_size,
964                target_size,
965            ),
966            Ar30ByteOrder::Network => self.plan_resize_ar30::<{ Ar30ByteOrder::Network as usize }>(
967                Rgb30::Ra30,
968                source_size,
969                target_size,
970            ),
971        }
972    }
973}
974
975/// Declares default scaling options
976#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Default)]
977pub struct ScalingOptions {
978    pub resampling_function: ResamplingFunction,
979    pub premultiply_alpha: bool,
980    pub threading_policy: ThreadingPolicy,
981}
982
983/// Generic trait for [ImageStore] to implement abstract scaling.
984pub trait ImageStoreScaling<'b, T, const N: usize>
985where
986    T: Clone + Copy + Debug,
987{
988    fn scale(
989        &self,
990        store: &mut ImageStoreMut<'b, T, N>,
991        options: ScalingOptions,
992    ) -> Result<(), PicScaleError>;
993}
994
995macro_rules! def_image_scaling_alpha {
996    ($clazz: ident, $fx_type: ident, $cn: expr) => {
997        impl<'b> ImageStoreScaling<'b, $fx_type, $cn> for $clazz<'b> {
998            fn scale(
999                &self,
1000                store: &mut ImageStoreMut<'b, $fx_type, $cn>,
1001                options: ScalingOptions,
1002            ) -> Result<(), PicScaleError> {
1003                let scaler = Scaler::new(options.resampling_function)
1004                    .set_threading_policy(options.threading_policy);
1005                let plan = scaler.plan_generic_resize_with_alpha::<$fx_type, f32, $cn>(
1006                    self.size(),
1007                    store.size(),
1008                    store.bit_depth,
1009                    options.premultiply_alpha,
1010                )?;
1011                plan.resample(self, store)
1012            }
1013        }
1014    };
1015}
1016
1017macro_rules! def_image_scaling {
1018    ($clazz: ident, $fx_type: ident, $cn: expr) => {
1019        impl<'b> ImageStoreScaling<'b, $fx_type, $cn> for $clazz<'b> {
1020            fn scale(
1021                &self,
1022                store: &mut ImageStoreMut<'b, $fx_type, $cn>,
1023                options: ScalingOptions,
1024            ) -> Result<(), PicScaleError> {
1025                let scaler = Scaler::new(options.resampling_function)
1026                    .set_threading_policy(options.threading_policy);
1027                let plan = scaler.plan_generic_resize::<$fx_type, f32, $cn>(
1028                    self.size(),
1029                    store.size(),
1030                    store.bit_depth,
1031                )?;
1032                plan.resample(self, store)
1033            }
1034        }
1035    };
1036}
1037
1038def_image_scaling_alpha!(Rgba8ImageStore, u8, 4);
1039def_image_scaling!(Rgb8ImageStore, u8, 3);
1040def_image_scaling!(CbCr8ImageStore, u8, 2);
1041def_image_scaling!(Planar8ImageStore, u8, 1);
1042def_image_scaling!(Planar16ImageStore, u16, 1);
1043def_image_scaling!(CbCr16ImageStore, u16, 2);
1044def_image_scaling!(Rgb16ImageStore, u16, 3);
1045def_image_scaling_alpha!(Rgba16ImageStore, u16, 4);
1046def_image_scaling!(PlanarF32ImageStore, f32, 1);
1047def_image_scaling!(CbCrF32ImageStore, f32, 2);
1048def_image_scaling!(RgbF32ImageStore, f32, 3);
1049def_image_scaling_alpha!(RgbaF32ImageStore, f32, 4);
1050
1051#[cfg(test)]
1052mod tests {
1053    use super::*;
1054
1055    macro_rules! check_rgba8 {
1056        ($dst: expr, $image_width: expr, $max: expr) => {
1057            {
1058                for (y, row) in $dst.chunks_exact($image_width * 4).enumerate() {
1059                    for (i, dst) in row.chunks_exact(4).enumerate() {
1060                        let diff0 = (dst[0] as i32 - 124).abs();
1061                        let diff1 = (dst[1] as i32 - 41).abs();
1062                        let diff2 = (dst[2] as i32 - 99).abs();
1063                        let diff3 = (dst[3] as i32 - 77).abs();
1064                        assert!(
1065                            diff0 < $max,
1066                            "Diff for channel 0 is expected < {}, but it was {diff0}, at (y: {y}, x: {i})",
1067                            $max
1068                        );
1069                        assert!(
1070                            diff1 < $max,
1071                            "Diff for channel 1 is expected < {}, but it was {diff1}, at (y: {y}, x: {i})",
1072                            $max
1073                        );
1074                        assert!(
1075                            diff2 < $max,
1076                            "Diff for channel 2 is expected < {}, but it was {diff2}, at (y: {y}, x: {i})",
1077                            $max
1078                        );
1079                        assert!(
1080                            diff3 < $max,
1081                            "Diff for channel 3 is expected < {}, but it was {diff3}, at (y: {y}, x: {i})",
1082                            $max
1083                        );
1084                    }
1085                }
1086            }
1087        };
1088    }
1089
1090    macro_rules! check_rgb16 {
1091        ($dst: expr, $image_width: expr, $max: expr) => {
1092            {
1093                for (y, row) in $dst.chunks_exact($image_width * 3).enumerate() {
1094                    for (i, dst) in row.chunks_exact(3).enumerate() {
1095                        let diff0 = (dst[0] as i32 - 124).abs();
1096                        let diff1 = (dst[1] as i32 - 41).abs();
1097                        let diff2 = (dst[2] as i32 - 99).abs();
1098                        assert!(
1099                            diff0 < $max,
1100                            "Diff for channel 0 is expected < {}, but it was {diff0}, at (y: {y}, x: {i})",
1101                            $max
1102                        );
1103                        assert!(
1104                            diff1 < $max,
1105                            "Diff for channel 1 is expected < {}, but it was {diff1}, at (y: {y}, x: {i})",
1106                            $max
1107                        );
1108                        assert!(
1109                            diff2 < $max,
1110                            "Diff for channel 2 is expected < {}, but it was {diff2}, at (y: {y}, x: {i})",
1111                            $max
1112                        );
1113                    }
1114                }
1115            }
1116        };
1117    }
1118
1119    #[test]
1120    fn check_rgba8_resizing_vertical() {
1121        let image_width = 255;
1122        let image_height = 512;
1123        const CN: usize = 4;
1124        let mut image = vec![0u8; image_height * image_width * CN];
1125        for dst in image.chunks_exact_mut(4) {
1126            dst[0] = 124;
1127            dst[1] = 41;
1128            dst[2] = 99;
1129            dst[3] = 77;
1130        }
1131        let scaler =
1132            Scaler::new(ResamplingFunction::Bilinear).set_threading_policy(ThreadingPolicy::Single);
1133        let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1134        let mut target_store = ImageStoreMut::alloc(image_width, image_height / 2);
1135        let planned = scaler
1136            .plan_rgba_resampling(src_store.size(), target_store.size(), false)
1137            .unwrap();
1138        planned.resample(&src_store, &mut target_store).unwrap();
1139        let target_data = target_store.buffer.borrow();
1140        check_rgba8!(target_data, image_width, 34);
1141    }
1142
1143    #[test]
1144    fn check_rgba8_resizing_both() {
1145        let image_width = 255;
1146        let image_height = 512;
1147        const CN: usize = 4;
1148        let mut image = vec![0u8; image_height * image_width * CN];
1149        for dst in image.chunks_exact_mut(4) {
1150            dst[0] = 124;
1151            dst[1] = 41;
1152            dst[2] = 99;
1153            dst[3] = 77;
1154        }
1155        image[3] = 78;
1156        let mut scaler = Scaler::new(ResamplingFunction::Bilinear);
1157        scaler.set_threading_policy(ThreadingPolicy::Single);
1158        let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1159        let mut target_store = ImageStoreMut::alloc(image_width / 2, image_height / 2);
1160        let planned = scaler
1161            .plan_rgba_resampling(src_store.size(), target_store.size(), false)
1162            .unwrap();
1163        planned.resample(&src_store, &mut target_store).unwrap();
1164        let target_data = target_store.buffer.borrow();
1165        check_rgba8!(target_data, image_width, 34);
1166    }
1167
1168    #[test]
1169    fn check_rgba8_resizing_alpha() {
1170        let image_width = 255;
1171        let image_height = 512;
1172        const CN: usize = 4;
1173        let mut image = vec![0u8; image_height * image_width * CN];
1174        for dst in image.chunks_exact_mut(4) {
1175            dst[0] = 124;
1176            dst[1] = 41;
1177            dst[2] = 99;
1178            dst[3] = 77;
1179        }
1180        image[3] = 78;
1181        let scaler =
1182            Scaler::new(ResamplingFunction::Lanczos3).set_threading_policy(ThreadingPolicy::Single);
1183        let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1184        let mut target_store = ImageStoreMut::alloc(image_width / 2, image_height / 2);
1185        let planned = scaler
1186            .plan_rgba_resampling(src_store.size(), target_store.size(), true)
1187            .unwrap();
1188        planned.resample(&src_store, &mut target_store).unwrap();
1189        let target_data = target_store.buffer.borrow();
1190        check_rgba8!(target_data, image_width, 160);
1191    }
1192
1193    #[test]
1194    fn check_rgb8_resizing_vertical() {
1195        let image_width = 255;
1196        let image_height = 512;
1197        const CN: usize = 3;
1198        let mut image = vec![0u8; image_height * image_width * CN];
1199        for dst in image.chunks_exact_mut(3) {
1200            dst[0] = 124;
1201            dst[1] = 41;
1202            dst[2] = 99;
1203        }
1204        let mut scaler = Scaler::new(ResamplingFunction::Bilinear);
1205        scaler.set_threading_policy(ThreadingPolicy::Single);
1206        let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1207        let mut target_store = ImageStoreMut::alloc(image_width, image_height / 2);
1208        let planned = scaler
1209            .plan_rgb_resampling(src_store.size(), target_store.size())
1210            .unwrap();
1211        planned.resample(&src_store, &mut target_store).unwrap();
1212        let target_data = target_store.buffer.borrow();
1213
1214        check_rgb16!(target_data, image_width, 85);
1215    }
1216
1217    #[test]
1218    fn check_rgb8_resizing_vertical_threading() {
1219        let image_width = 255;
1220        let image_height = 512;
1221        const CN: usize = 3;
1222        let mut image = vec![0u8; image_height * image_width * CN];
1223        for dst in image.chunks_exact_mut(3) {
1224            dst[0] = 124;
1225            dst[1] = 41;
1226            dst[2] = 99;
1227        }
1228        let mut scaler = Scaler::new(ResamplingFunction::Bilinear);
1229        scaler.set_threading_policy(ThreadingPolicy::Adaptive);
1230        let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1231        let mut target_store = ImageStoreMut::alloc(image_width, image_height / 2);
1232        let planned = scaler
1233            .plan_rgb_resampling(src_store.size(), target_store.size())
1234            .unwrap();
1235        planned.resample(&src_store, &mut target_store).unwrap();
1236        let target_data = target_store.buffer.borrow();
1237
1238        check_rgb16!(target_data, image_width, 85);
1239    }
1240
1241    #[test]
1242    fn check_rgba10_resizing_vertical() {
1243        let image_width = 8;
1244        let image_height = 8;
1245        const CN: usize = 4;
1246        let mut image = vec![0u16; image_height * image_width * CN];
1247        for dst in image.chunks_exact_mut(4) {
1248            dst[0] = 124;
1249            dst[1] = 41;
1250            dst[2] = 99;
1251            dst[3] = 77;
1252        }
1253        image[3] = 78;
1254        let mut scaler = Scaler::new(ResamplingFunction::Lanczos3);
1255        scaler.set_threading_policy(ThreadingPolicy::Single);
1256        let mut src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1257        src_store.bit_depth = 10;
1258        let mut target_store = ImageStoreMut::alloc_with_depth(image_width, image_height / 2, 10);
1259        let planned = scaler
1260            .plan_rgba_resampling16(src_store.size(), target_store.size(), true, 10)
1261            .unwrap();
1262        planned.resample(&src_store, &mut target_store).unwrap();
1263        let target_data = target_store.buffer.borrow();
1264
1265        check_rgba8!(target_data, image_width, 60);
1266    }
1267
1268    #[test]
1269    fn check_rgb10_resizing_vertical() {
1270        let image_width = 8;
1271        let image_height = 4;
1272        const CN: usize = 3;
1273        let mut image = vec![0; image_height * image_width * CN];
1274        for dst in image.chunks_exact_mut(3) {
1275            dst[0] = 124;
1276            dst[1] = 41;
1277            dst[2] = 99;
1278        }
1279        let mut scaler = Scaler::new(ResamplingFunction::Lanczos3);
1280        scaler.set_threading_policy(ThreadingPolicy::Single);
1281        let mut src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1282        src_store.bit_depth = 10;
1283        let mut target_store = ImageStoreMut::alloc_with_depth(image_width, image_height / 2, 10);
1284        let planned = scaler
1285            .plan_rgb_resampling16(src_store.size(), target_store.size(), 10)
1286            .unwrap();
1287        planned.resample(&src_store, &mut target_store).unwrap();
1288        let target_data = target_store.buffer.borrow();
1289
1290        check_rgb16!(target_data, image_width, 85);
1291    }
1292
1293    #[test]
1294    fn check_rgb10_resizing_vertical_adaptive() {
1295        let image_width = 8;
1296        let image_height = 4;
1297        const CN: usize = 3;
1298        let mut image = vec![0; image_height * image_width * CN];
1299        for dst in image.chunks_exact_mut(3) {
1300            dst[0] = 124;
1301            dst[1] = 41;
1302            dst[2] = 99;
1303        }
1304        let mut scaler = Scaler::new(ResamplingFunction::Lanczos3);
1305        scaler.set_threading_policy(ThreadingPolicy::Adaptive);
1306        let mut src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1307        src_store.bit_depth = 10;
1308        let mut target_store = ImageStoreMut::alloc_with_depth(image_width, image_height / 2, 10);
1309        let planned = scaler
1310            .plan_rgb_resampling16(src_store.size(), target_store.size(), 10)
1311            .unwrap();
1312        planned.resample(&src_store, &mut target_store).unwrap();
1313        let target_data = target_store.buffer.borrow();
1314
1315        check_rgb16!(target_data, image_width, 85);
1316    }
1317
1318    #[test]
1319    fn check_rgb16_resizing_vertical() {
1320        let image_width = 8;
1321        let image_height = 8;
1322        const CN: usize = 3;
1323        let mut image = vec![164; image_height * image_width * CN];
1324        for dst in image.chunks_exact_mut(3) {
1325            dst[0] = 124;
1326            dst[1] = 41;
1327            dst[2] = 99;
1328        }
1329        let mut scaler = Scaler::new(ResamplingFunction::Lanczos3);
1330        scaler.set_threading_policy(ThreadingPolicy::Single);
1331        let mut src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1332        src_store.bit_depth = 10;
1333        let mut target_store = ImageStoreMut::alloc_with_depth(image_width, image_height / 2, 16);
1334        let planned = scaler
1335            .plan_rgb_resampling16(src_store.size(), target_store.size(), 16)
1336            .unwrap();
1337        planned.resample(&src_store, &mut target_store).unwrap();
1338        let target_data = target_store.buffer.borrow();
1339
1340        check_rgb16!(target_data, image_width, 100);
1341    }
1342
1343    #[test]
1344    fn check_rgba16_resizing_vertical() {
1345        let image_width = 8;
1346        let image_height = 8;
1347        const CN: usize = 4;
1348        let mut image = vec![0u16; image_height * image_width * CN];
1349        for dst in image.chunks_exact_mut(4) {
1350            dst[0] = 124;
1351            dst[1] = 41;
1352            dst[2] = 99;
1353            dst[3] = 255;
1354        }
1355        let mut scaler = Scaler::new(ResamplingFunction::Lanczos3);
1356        scaler.set_threading_policy(ThreadingPolicy::Single);
1357        let mut src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1358        src_store.bit_depth = 10;
1359        let mut target_store = ImageStoreMut::alloc_with_depth(image_width, image_height / 2, 16);
1360        let planned = scaler
1361            .plan_rgba_resampling16(src_store.size(), target_store.size(), false, 16)
1362            .unwrap();
1363        planned.resample(&src_store, &mut target_store).unwrap();
1364        let target_data = target_store.buffer.borrow();
1365
1366        check_rgba8!(target_data, image_width, 180);
1367    }
1368
1369    #[test]
1370    fn check_rgba16_resizing_vertical_threading() {
1371        let image_width = 8;
1372        let image_height = 8;
1373        const CN: usize = 4;
1374        let mut image = vec![0u16; image_height * image_width * CN];
1375        for dst in image.chunks_exact_mut(4) {
1376            dst[0] = 124;
1377            dst[1] = 41;
1378            dst[2] = 99;
1379            dst[3] = 255;
1380        }
1381        let scaler = Scaler::new(ResamplingFunction::Lanczos3)
1382            .set_threading_policy(ThreadingPolicy::Adaptive);
1383        let mut src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1384        src_store.bit_depth = 10;
1385        let mut target_store = ImageStoreMut::alloc_with_depth(image_width, image_height / 2, 16);
1386        let planned = scaler
1387            .plan_rgba_resampling16(src_store.size(), target_store.size(), false, 16)
1388            .unwrap();
1389        planned.resample(&src_store, &mut target_store).unwrap();
1390        let target_data = target_store.buffer.borrow();
1391
1392        check_rgba8!(target_data, image_width, 180);
1393    }
1394
1395    #[test]
1396    fn check_rgba8_nearest_vertical() {
1397        let image_width = 255;
1398        let image_height = 512;
1399        const CN: usize = 4;
1400        let mut image = vec![0u8; image_height * image_width * CN];
1401        for dst in image.chunks_exact_mut(4) {
1402            dst[0] = 124;
1403            dst[1] = 41;
1404            dst[2] = 99;
1405            dst[3] = 77;
1406        }
1407        let mut scaler = Scaler::new(ResamplingFunction::Nearest);
1408        scaler.set_threading_policy(ThreadingPolicy::Single);
1409        let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1410        let mut target_store = ImageStoreMut::alloc(image_width, image_height / 2);
1411        let planned = scaler
1412            .plan_rgba_resampling(src_store.size(), target_store.size(), false)
1413            .unwrap();
1414        planned.resample(&src_store, &mut target_store).unwrap();
1415        let target_data = target_store.buffer.borrow();
1416
1417        check_rgba8!(target_data, image_width, 80);
1418    }
1419
1420    #[test]
1421    fn check_rgba8_nearest_vertical_threading() {
1422        let image_width = 255;
1423        let image_height = 512;
1424        const CN: usize = 4;
1425        let mut image = vec![0u8; image_height * image_width * CN];
1426        for dst in image.chunks_exact_mut(4) {
1427            dst[0] = 124;
1428            dst[1] = 41;
1429            dst[2] = 99;
1430            dst[3] = 77;
1431        }
1432        let mut scaler = Scaler::new(ResamplingFunction::Nearest);
1433        scaler.set_threading_policy(ThreadingPolicy::Adaptive);
1434        let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1435        let mut target_store = ImageStoreMut::alloc(image_width, image_height / 2);
1436        let planned = scaler
1437            .plan_rgba_resampling(src_store.size(), target_store.size(), false)
1438            .unwrap();
1439        planned.resample(&src_store, &mut target_store).unwrap();
1440        let target_data = target_store.buffer.borrow();
1441
1442        check_rgba8!(target_data, image_width, 80);
1443    }
1444}