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