Skip to main content

fer/
resizer.rs

1use std::num::NonZeroU32;
2
3use crate::convolution::{self, Convolution, FilterType};
4use crate::image::InnerImage;
5use crate::pixels::PixelExt;
6use crate::{ImageView, ImageViewMut};
7
8/// SIMD extension of CPU.
9/// Specific variants depends from target architecture.
10/// Look at source code to see all available variants.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum CpuExtensions {
13    None,
14    #[cfg(target_arch = "x86_64")]
15    /// SIMD extension of x86_64 architecture
16    Sse4_1,
17    #[cfg(target_arch = "x86_64")]
18    /// SIMD extension of x86_64 architecture
19    Avx2,
20    #[cfg(target_arch = "aarch64")]
21    /// SIMD extension of Arm64 architecture
22    Neon,
23    #[cfg(target_arch = "wasm32")]
24    /// SIMD extension of Wasm32 architecture
25    Simd128,
26}
27
28impl CpuExtensions {
29    /// Returns `true` if your CPU support the extension.
30    pub fn is_supported(&self) -> bool {
31        match self {
32            #[cfg(target_arch = "x86_64")]
33            Self::Avx2 => is_x86_feature_detected!("avx2"),
34            #[cfg(target_arch = "x86_64")]
35            Self::Sse4_1 => is_x86_feature_detected!("sse4.1"),
36            #[cfg(target_arch = "aarch64")]
37            Self::Neon => true,
38            #[cfg(target_arch = "wasm32")]
39            Self::Simd128 => true,
40            Self::None => true,
41        }
42    }
43}
44
45impl Default for CpuExtensions {
46    #[cfg(target_arch = "x86_64")]
47    fn default() -> Self {
48        if is_x86_feature_detected!("avx2") {
49            Self::Avx2
50        } else if is_x86_feature_detected!("sse4.1") {
51            Self::Sse4_1
52        } else {
53            Self::None
54        }
55    }
56
57    #[cfg(target_arch = "aarch64")]
58    fn default() -> Self {
59        use std::arch::is_aarch64_feature_detected;
60        if is_aarch64_feature_detected!("neon") {
61            Self::Neon
62        } else {
63            Self::None
64        }
65    }
66    #[cfg(target_arch = "wasm32")]
67    fn default() -> Self {
68        Self::Simd128
69    }
70
71    #[cfg(not(any(
72        target_arch = "x86_64",
73        target_arch = "aarch64",
74        target_arch = "wasm32"
75    )))]
76    fn default() -> Self {
77        Self::None
78    }
79}
80
81#[derive(Debug, Clone, Copy)]
82#[non_exhaustive]
83pub enum ResizeAlg {
84    Nearest,
85    Convolution(FilterType),
86    SuperSampling(FilterType, u8),
87}
88
89impl Default for ResizeAlg {
90    fn default() -> Self {
91        Self::Convolution(FilterType::Lanczos3)
92    }
93}
94
95/// Methods of this structure used to resize images.
96#[derive(Default, Debug, Clone)]
97pub struct Resizer {
98    pub algorithm: ResizeAlg,
99    cpu_extensions: CpuExtensions,
100    convolution_buffer: Vec<u8>,
101    super_sampling_buffer: Vec<u8>,
102}
103
104impl Resizer {
105    /// Creates instance of `Resizer`
106    ///
107    /// By default, instance of `Resizer` created with best CPU-extensions provided by your CPU.
108    /// You can change this by use method [Resizer::set_cpu_extensions].
109    pub fn new(algorithm: ResizeAlg) -> Self {
110        Self {
111            algorithm,
112            ..Default::default()
113        }
114    }
115
116    /// Resize source image to the size of destination image and save
117    /// the result to the latter's pixel buffer.
118    ///
119    /// This method doesn't multiply source image and doesn't divide
120    /// destination image by alpha channel.
121    /// You must use [MulDiv](crate::MulDiv) for these actions.
122    pub unsafe fn resize<P>(&mut self, src_image: &ImageView<P>, dst_image: &mut ImageViewMut<P>)
123    where
124        P: Convolution,
125    {
126        if {
127            let src_crop_box = src_image.crop_box();
128            dst_image.width == src_crop_box.width && dst_image.height == src_crop_box.height
129        } {
130            return;
131        }
132        match self.algorithm {
133            ResizeAlg::Nearest => resample_nearest(src_image, dst_image),
134            ResizeAlg::Convolution(filter_type) => {
135                let convolution_buffer = &mut self.convolution_buffer;
136                resample_convolution(
137                    src_image,
138                    dst_image,
139                    filter_type,
140                    self.cpu_extensions,
141                    convolution_buffer,
142                )
143            }
144            ResizeAlg::SuperSampling(filter_type, multiplicity) => {
145                let convolution_buffer = &mut self.convolution_buffer;
146                let super_sampling_buffer = &mut self.super_sampling_buffer;
147                resample_super_sampling(
148                    src_image,
149                    dst_image,
150                    filter_type,
151                    multiplicity,
152                    self.cpu_extensions,
153                    super_sampling_buffer,
154                    convolution_buffer,
155                )
156            }
157        }
158    }
159
160    /// Returns the size of internal buffers used to store the results of
161    /// intermediate resizing steps.
162    pub fn size_of_internal_buffers(&self) -> usize {
163        (self.convolution_buffer.capacity() + self.super_sampling_buffer.capacity())
164            * std::mem::size_of::<u8>()
165    }
166
167    /// Deallocates the internal buffers used to store the results of
168    /// intermediate resizing steps.
169    pub fn reset_internal_buffers(&mut self) {
170        if self.convolution_buffer.capacity() > 0 {
171            self.convolution_buffer = Vec::new();
172        }
173        if self.super_sampling_buffer.capacity() > 0 {
174            self.super_sampling_buffer = Vec::new();
175        }
176    }
177
178    #[inline(always)]
179    pub fn cpu_extensions(&self) -> CpuExtensions {
180        self.cpu_extensions
181    }
182
183    /// # Safety
184    /// This is unsafe because this method allows you to set a CPU-extensions
185    /// that is not actually supported by your CPU.
186    pub unsafe fn set_cpu_extensions(&mut self, extensions: CpuExtensions) {
187        self.cpu_extensions = extensions;
188    }
189}
190
191/// Create inner image container from part of given buffer.
192/// Buffer may be expanded if it size is less than required for image.
193fn get_temp_image_from_buffer<P: PixelExt>(
194    buffer: &mut Vec<u8>,
195    width: NonZeroU32,
196    height: NonZeroU32,
197) -> InnerImage<P> {
198    let pixels_count = (width.get() * height.get()) as usize;
199    // Add pixel size as gap for alignment of resulted buffer.
200    let buf_size = pixels_count * P::size() + P::size();
201    if buffer.len() < buf_size {
202        buffer.resize(buf_size, 0);
203    }
204    let pixels = unsafe { buffer.align_to_mut::<P>().1 };
205    InnerImage::new(width, height, &mut pixels[0..pixels_count])
206}
207
208fn resample_nearest<P>(src_image: &ImageView<P>, dst_image: &mut ImageViewMut<P>)
209where
210    P: PixelExt,
211{
212    let crop_box = src_image.crop_box();
213    let dst_width = dst_image.width().get();
214    let x_scale = crop_box.width.get() as f64 / dst_width as f64;
215    let y_scale = crop_box.height.get() as f64 / dst_image.height().get() as f64;
216
217    // Pretabulate horizontal pixel positions
218    let x_in_start = crop_box.left as f64 + x_scale * 0.5;
219    let max_src_x = src_image.width().get() as usize;
220    let x_in_tab: Vec<usize> = (0..dst_width)
221        .map(|x| ((x_in_start + x_scale * x as f64) as usize).min(max_src_x))
222        .collect();
223
224    let y_in_start = crop_box.top as f64 + y_scale * 0.5;
225
226    let src_rows =
227        src_image.iter_rows_with_step(y_in_start, y_scale, dst_image.height().get() as usize);
228    let dst_rows = dst_image.iter_rows_mut();
229    for (out_row, in_row) in dst_rows.zip(src_rows) {
230        for (&x_in, out_pixel) in x_in_tab.iter().zip(out_row.iter_mut()) {
231            // Safety of value of x_in guaranteed by algorithm of creating of x_in_tab
232            *out_pixel = unsafe { *in_row.get_unchecked(x_in) };
233        }
234    }
235}
236
237fn resample_convolution<P>(
238    src_image: &ImageView<P>,
239    dst_image: &mut ImageViewMut<P>,
240    filter_type: FilterType,
241    cpu_extensions: CpuExtensions,
242    temp_buffer: &mut Vec<u8>,
243) where
244    P: Convolution,
245{
246    let crop_box = src_image.crop_box();
247    let dst_width = dst_image.width();
248    let dst_height = dst_image.height();
249    let (filter_fn, filter_support) = convolution::get_filter_func(filter_type);
250
251    let need_horizontal = dst_width != crop_box.width;
252    let horiz_coeffs = need_horizontal.then(|| {
253        convolution::precompute_coefficients(
254            src_image.width(),
255            crop_box.left as f64,
256            crop_box.left as f64 + crop_box.width.get() as f64,
257            dst_width,
258            filter_fn,
259            filter_support,
260        )
261    });
262
263    let need_vertical = dst_height != crop_box.height;
264    let vert_coeffs = need_vertical.then(|| {
265        convolution::precompute_coefficients(
266            src_image.height(),
267            crop_box.top as f64,
268            crop_box.top as f64 + crop_box.height.get() as f64,
269            dst_height,
270            filter_fn,
271            filter_support,
272        )
273    });
274
275    match (horiz_coeffs, vert_coeffs) {
276        (Some(horiz_coeffs), Some(mut vert_coeffs)) => {
277            let y_first = vert_coeffs.bounds[0].start;
278            // Last used row in the source image
279            let last_y_bound = vert_coeffs.bounds.last().unwrap();
280            let y_last = last_y_bound.start + last_y_bound.size;
281            let temp_height = NonZeroU32::new(y_last - y_first).unwrap();
282            let mut temp_image = get_temp_image_from_buffer(temp_buffer, dst_width, temp_height);
283            let mut tmp_dst_view = unsafe { temp_image.dst_view() };
284            P::horiz_convolution(
285                src_image,
286                &mut tmp_dst_view,
287                y_first,
288                horiz_coeffs,
289                cpu_extensions,
290            );
291
292            // Shift bounds for vertical pass
293            vert_coeffs
294                .bounds
295                .iter_mut()
296                .for_each(|b| b.start -= y_first);
297            P::vert_convolution(
298                &tmp_dst_view.into(),
299                dst_image,
300                0,
301                vert_coeffs,
302                cpu_extensions,
303            );
304        }
305        (Some(horiz_coeffs), None) => {
306            P::horiz_convolution(
307                src_image,
308                dst_image,
309                crop_box.top,
310                horiz_coeffs,
311                cpu_extensions,
312            );
313        }
314        (None, Some(vert_coeffs)) => {
315            P::vert_convolution(
316                src_image,
317                dst_image,
318                crop_box.left,
319                vert_coeffs,
320                cpu_extensions,
321            );
322        }
323        _ => {}
324    }
325}
326
327fn resample_super_sampling<P>(
328    src_image: &ImageView<P>,
329    dst_image: &mut ImageViewMut<P>,
330    filter_type: FilterType,
331    multiplicity: u8,
332    cpu_extensions: CpuExtensions,
333    temp_buffer: &mut Vec<u8>,
334    convolution_temp_buffer: &mut Vec<u8>,
335) where
336    P: Convolution,
337{
338    let crop_box = src_image.crop_box();
339    let dst_width = dst_image.width().get();
340    let dst_height = dst_image.height().get();
341    let width_scale = crop_box.width.get() as f32 / dst_width as f32;
342    let height_scale = crop_box.height.get() as f32 / dst_height as f32;
343    // It makes sense to resize the image in two steps only if the image
344    // size is greater than the required size by multiplicity times.
345    let factor = width_scale.min(height_scale) / multiplicity as f32;
346    if factor > 1.2 {
347        // First step is resizing the source image by fastest algorithm.
348        // The temporary image will be about ``multiplicity`` times larger
349        // than required.
350        let tmp_width =
351            NonZeroU32::new((crop_box.width.get() as f32 / factor).round() as u32).unwrap();
352        let tmp_height =
353            NonZeroU32::new((crop_box.height.get() as f32 / factor).round() as u32).unwrap();
354
355        let mut tmp_img = get_temp_image_from_buffer(temp_buffer, tmp_width, tmp_height);
356        resample_nearest(src_image, unsafe { &mut tmp_img.dst_view() });
357        // Second step is resizing the temporary image with a convolution.
358        resample_convolution(
359            unsafe { &tmp_img.src_view() },
360            dst_image,
361            filter_type,
362            cpu_extensions,
363            convolution_temp_buffer,
364        );
365    } else {
366        // There is no point in doing the resizing in two steps.
367        // We immediately resize the original image with a convolution.
368        resample_convolution(
369            src_image,
370            dst_image,
371            filter_type,
372            cpu_extensions,
373            convolution_temp_buffer,
374        );
375    }
376}