1use crate::prelude::*;
2use crate::util::{dimension_to_usize, index_point};
3use crate::Dimension;
4
5pub mod filters;
7
8pub trait Processable: Copy {
11 fn to_f32(self) -> f32;
13
14 fn from_f32(value: f32) -> Self;
20}
21
22macro_rules! impl_processable {
23 ($($type:ty),*) => {
24 $(
25 impl Processable for $type {
26 #[inline(always)]
27 fn to_f32(self) -> f32 {
28 self as f32
29 }
30
31 #[inline(always)]
32 fn from_f32(value: f32) -> Self {
33 value.clamp(Self::MIN as f32, Self::MAX as f32) as Self
34 }
35 }
36 )*
37 };
38}
39
40impl_processable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, usize, isize);
41
42#[must_use = "the resampled buffer is returned and the original view is left unmodified"]
52pub fn resample_horizontal<I, P, C, F, const N: usize>(
53 view: &I,
54 width: Dimension,
55 filter: F,
56 window: f32,
57) -> ImgBuf<P, Vec<P>>
58where
59 I: ImgView<Pixel = P>,
60 P: Pixel<Channels = [C; N]>,
61 C: Processable,
62 F: Fn(f32) -> f32,
63{
64 if width == 0 {
65 return ImgBuf::from_container(Vec::new(), width, view.height());
66 }
67
68 let mut container =
70 Vec::with_capacity(dimension_to_usize(width) * dimension_to_usize(view.height()));
71 let container_pixels = container.spare_capacity_mut();
72
73 let ratio = view.width() as f32 / width as f32;
75 let sampling_ratio = ratio.max(1.0);
76 let inverse_sampling_ratio = 1.0 / sampling_ratio;
77
78 let window = window * sampling_ratio;
86
87 let max_src_x_f32 = (view.width() - 1) as f32;
89 let mut weights = Vec::with_capacity((2 * (window as usize) + 1) * dimension_to_usize(width));
90 let mut weights_start_index = Vec::with_capacity(width as usize);
91 for target_x in 0..width {
92 let equivalent_src_x = target_x as f32 * ratio + 0.5 * (ratio - 1.0);
93
94 let min_src_pixel_x = (equivalent_src_x - window).clamp(0.0, max_src_x_f32) as Dimension;
95 let max_src_pixel_x = (equivalent_src_x + window).clamp(0.0, max_src_x_f32) as Dimension;
96
97 weights_start_index.push(weights.len());
98 for src_pixel_x in min_src_pixel_x..=max_src_pixel_x {
99 weights.push(filter(
100 (src_pixel_x as f32 - equivalent_src_x) * inverse_sampling_ratio,
101 ));
102 }
103 }
104
105 for target_x in 0..width {
107 let equivalent_src_x = target_x as f32 * ratio + (1.0 - 1.0 / ratio) / (2.0 / ratio);
110
111 let min_src_pixel_x = (equivalent_src_x - window).clamp(0.0, max_src_x_f32) as Dimension;
112 let max_src_pixel_x = (equivalent_src_x + window).clamp(0.0, max_src_x_f32) as Dimension;
113
114 let weights_start = weights_start_index[target_x as usize];
115 for target_y in 0..view.height() {
116 let mut weight_sum = 0f32;
117 let mut channel_value_sum = [0f32; N];
118 for (index, src_pixel_x) in (min_src_pixel_x..=max_src_pixel_x).enumerate() {
119 let src_pixel = unsafe { view.pixel_unchecked((src_pixel_x, target_y)) };
122 let channels = src_pixel.channels();
123 let weight = weights[weights_start + index];
124 weight_sum += weight;
125
126 for channel_index in 0..N {
127 let value = weight * channels[channel_index].to_f32();
128 channel_value_sum[channel_index] += value;
129 }
130 }
131
132 let result: arrayvec::ArrayVec<_, N> = channel_value_sum
133 .into_iter()
134 .map(|v| C::from_f32(v / weight_sum))
135 .collect();
136
137 unsafe {
140 container_pixels
141 .get_unchecked_mut(index_point((target_x, target_y), width))
142 .write(P::new(result.into_inner_unchecked()));
143 }
144 }
145 }
146
147 unsafe {
149 let size = dimension_to_usize(width) * dimension_to_usize(view.height());
150 container.set_len(size);
151 }
152
153 ImgBuf::from_container(container, width, view.height())
154}
155
156#[must_use = "the resampled buffer is returned and the original view is left unmodified"]
162pub fn resample_vertical<I, P, C, F, const N: usize>(
163 view: &I,
164 height: Dimension,
165 filter: F,
166 window: f32,
167) -> ImgBuf<P, Vec<P>>
168where
169 I: ImgView<Pixel = P>,
170 P: Pixel<Channels = [C; N]>,
171 C: Processable,
172 F: Fn(f32) -> f32,
173{
174 if height == 0 {
175 return ImgBuf::from_container(Vec::new(), view.width(), height);
176 }
177
178 let mut container =
180 Vec::with_capacity(dimension_to_usize(height) * dimension_to_usize(view.width()));
181 let container_pixels = container.spare_capacity_mut();
182
183 let ratio = view.height() as f32 / height as f32;
185 let sampling_ratio = ratio.max(1.0);
186 let inverse_sampling_ratio = 1.0 / sampling_ratio;
187
188 let window = window * sampling_ratio;
196
197 let max_src_y_f32 = (view.height() - 1) as f32;
199 let mut weights = Vec::with_capacity((2 * (window as usize) + 1) * dimension_to_usize(height));
200 let mut weights_start_index = Vec::with_capacity(height as usize);
201 for target_y in 0..height {
202 let equivalent_src_y = target_y as f32 * ratio + 0.5 * (ratio - 1.0);
203
204 let min_src_pixel_y = (equivalent_src_y - window).clamp(0.0, max_src_y_f32) as Dimension;
205 let max_src_pixel_y = (equivalent_src_y + window).clamp(0.0, max_src_y_f32) as Dimension;
206
207 weights_start_index.push(weights.len());
208 for src_pixel_y in min_src_pixel_y..=max_src_pixel_y {
209 weights.push(filter(
210 (src_pixel_y as f32 - equivalent_src_y) * inverse_sampling_ratio,
211 ));
212 }
213 }
214
215 for target_y in 0..height {
217 let equivalent_src_y = target_y as f32 * ratio + 0.5 * (ratio - 1.0);
220
221 let min_src_pixel_y = (equivalent_src_y - window).clamp(0.0, max_src_y_f32) as Dimension;
222 let max_src_pixel_y = (equivalent_src_y + window).clamp(0.0, max_src_y_f32) as Dimension;
223
224 let weights_start = weights_start_index[target_y as usize];
225 for target_x in 0..view.width() {
226 let mut weight_sum = 0f32;
227 let mut channel_value_sum = [0f32; N];
228 for (index, src_pixel_y) in (min_src_pixel_y..=max_src_pixel_y).enumerate() {
229 let src_pixel = unsafe { view.pixel_unchecked((target_x, src_pixel_y)) };
232 let channels = src_pixel.channels();
233 let weight = weights[weights_start + index];
234 weight_sum += weight;
235
236 for channel_index in 0..N {
237 let value = weight * channels[channel_index].to_f32();
238 channel_value_sum[channel_index] += value;
239 }
240 }
241
242 let result: arrayvec::ArrayVec<_, N> = channel_value_sum
243 .into_iter()
244 .map(|v| C::from_f32(v / weight_sum))
245 .collect();
246
247 unsafe {
250 container_pixels
251 .get_unchecked_mut(index_point((target_x, target_y), view.width()))
252 .write(P::new(result.into_inner_unchecked()));
253 }
254 }
255 }
256
257 unsafe {
259 let size = dimension_to_usize(height) * dimension_to_usize(view.width());
260 container.set_len(size);
261 }
262
263 ImgBuf::from_container(container, view.width(), height)
264}
265
266#[must_use = "the resampled buffer is returned and the original view is left unmodified"]
272pub fn resample<I, P, C, F, const N: usize>(
273 view: &I,
274 (width, height): (Dimension, Dimension),
275 filter: F,
276 window: f32,
277) -> ImgBuf<P, Vec<P>>
278where
279 I: ImgView<Pixel = P>,
280 P: Pixel<Channels = [C; N]>,
281 C: Processable,
282 F: Fn(f32) -> f32,
283{
284 let horizontal = resample_horizontal(view, width, &filter, window);
285 resample_vertical(&horizontal, height, filter, window)
286}
287
288#[must_use = "the blurred buffer is returned and the original view is left unmodified"]
290pub fn box_blur<I, P, C, const N: usize>(view: &I, strength: f32) -> ImgBuf<P, Vec<P>>
291where
292 I: ImgView<Pixel = P>,
293 P: Pixel<Channels = [C; N]>,
294 C: Processable,
295{
296 assert!(strength > 0.0);
297 resample(view, view.dimensions(), filters::box_filter, strength)
298}
299
300#[must_use = "the blurred buffer is returned and the original view is left unmodified"]
302pub fn gaussian_blur<I, P, C, const N: usize>(view: &I, strength: f32) -> ImgBuf<P, Vec<P>>
303where
304 I: ImgView<Pixel = P>,
305 P: Pixel<Channels = [C; N]>,
306 C: Processable,
307{
308 assert!(strength > 0.0);
309 resample(
310 view,
311 view.dimensions(),
312 |x| filters::gaussian(x, strength),
313 2.0 * strength,
314 )
315}
316
317#[derive(Debug, Clone, Copy, PartialEq)]
319pub enum ResizeFilter {
320 Box,
321 Triangle,
322 BSpline,
323 Mitchell,
324 CatmullRom,
325 Lanczos2,
326 Lanczos3,
327}
328
329#[must_use = "the resized buffer is returned and the original view is left unmodified"]
331pub fn resize<I, P, C, const N: usize>(
332 view: &I,
333 dimensions: (Dimension, Dimension),
334 filter: ResizeFilter,
335) -> ImgBuf<P, Vec<P>>
336where
337 I: ImgView<Pixel = P>,
338 P: Pixel<Channels = [C; N]>,
339 C: Processable,
340{
341 match filter {
342 ResizeFilter::Box => resample(view, dimensions, filters::box_filter, 0.0),
343 ResizeFilter::Triangle => resample(view, dimensions, filters::triangle, 1.0),
344 ResizeFilter::BSpline => resample(view, dimensions, filters::b_spline, 2.0),
345 ResizeFilter::Mitchell => resample(view, dimensions, filters::mitchell, 2.0),
346 ResizeFilter::CatmullRom => resample(view, dimensions, filters::catmull_rom, 2.0),
347 ResizeFilter::Lanczos2 => resample(view, dimensions, filters::lanczos2, 2.0),
348 ResizeFilter::Lanczos3 => resample(view, dimensions, filters::lanczos3, 3.0),
349 }
350}
351
352pub fn flip_horizontal<I>(view: &mut I)
354where
355 I: ImgViewMut,
356{
357 for y in 0..view.height() {
358 for x in 0..(view.width() / 2) {
359 let left_pixel_bounds = Rect::new((x, y), (1, 1));
360 let right_pixel_bounds = Rect::new((view.width() - 1 - x, y), (1, 1));
361
362 let [mut left, mut right] = view
363 .view_mut_multiple([left_pixel_bounds, right_pixel_bounds])
364 .unwrap();
365
366 std::mem::swap(
367 left.pixel_mut((0, 0)).unwrap(),
368 right.pixel_mut((0, 0)).unwrap(),
369 );
370 }
371 }
372}
373
374pub fn flip_vertical<I>(view: &mut I)
376where
377 I: ImgViewMut,
378{
379 for x in 0..view.width() {
380 for y in 0..(view.height() / 2) {
381 let top_pixel_bounds = Rect::new((x, y), (1, 1));
382 let bottom_pixel_bounds = Rect::new((x, view.height() - 1 - y), (1, 1));
383
384 let [mut top, mut bottom] = view
385 .view_mut_multiple([top_pixel_bounds, bottom_pixel_bounds])
386 .unwrap();
387
388 std::mem::swap(
389 top.pixel_mut((0, 0)).unwrap(),
390 bottom.pixel_mut((0, 0)).unwrap(),
391 );
392 }
393 }
394}