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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum CpuExtensions {
13 None,
14 #[cfg(target_arch = "x86_64")]
15 Sse4_1,
17 #[cfg(target_arch = "x86_64")]
18 Avx2,
20 #[cfg(target_arch = "aarch64")]
21 Neon,
23 #[cfg(target_arch = "wasm32")]
24 Simd128,
26}
27
28impl CpuExtensions {
29 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#[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 pub fn new(algorithm: ResizeAlg) -> Self {
110 Self {
111 algorithm,
112 ..Default::default()
113 }
114 }
115
116 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 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 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 pub unsafe fn set_cpu_extensions(&mut self, extensions: CpuExtensions) {
187 self.cpu_extensions = extensions;
188 }
189}
190
191fn 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 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 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 *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 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 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 let factor = width_scale.min(height_scale) / multiplicity as f32;
346 if factor > 1.2 {
347 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 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 resample_convolution(
369 src_image,
370 dst_image,
371 filter_type,
372 cpu_extensions,
373 convolution_temp_buffer,
374 );
375 }
376}