Skip to main content

zune_imageprocs/
resize.rs

1/*
2 * Copyright (c) 2023.
3 *
4 * This software is free software;
5 *
6 * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license
7 */
8
9//! Resize an image to a new width and height
10//!
11//!
12//! Currently only implements a simple bilinear resizer, future plans are to have
13//! more complicated resizers implemented.
14//!
15//!
16
17use std::cmp::PartialEq;
18use std::time::Instant;
19
20use zune_core::bit_depth::{BitDepth, BitType};
21use zune_core::colorspace::ColorCharacteristics;
22use zune_core::log::trace;
23use zune_image::channel::Channel;
24use zune_image::errors::ImageErrors;
25use zune_image::image::Image;
26use zune_image::metadata::AlphaState;
27use zune_image::traits::OperationsTrait;
28
29use crate::image_transfer::{ConversionType, ImageTransfer, TransferFunction};
30use crate::premul_alpha::PremultiplyAlpha;
31use crate::resize::seperable_kernel::PrecomputedKernels;
32use crate::traits::NumOps;
33use crate::utils::execute_on;
34
35mod bilinear;
36mod seperable_kernel;
37
38#[derive(Copy, Clone, Debug, Eq, PartialEq)]
39pub enum ResizeMethod {
40    Lanczos3,   // Lanczos with a=3 (highest quality, slowest)
41    Lanczos2,   // Lanczos with a=2
42    Bicubic,    // Bicubic interpolation (Mitchell-Netravali, B=1/3, C=1/3)
43    CatmullRom, // Catmull-Rom spline (B=0, C=0.5)
44    Mitchell,   // Mitchell filter (B=1/3, C=1/3) - same as Bicubic but explicit
45    BSpline,    // B-Spline (B=1, C=0)
46    Hermite,    // Hermite filter (B=0, C=0)
47    Sinc,       // Sinc with window radius 3
48    Bilinear    // Bilinear (for completeness, 2x2 kernel)
49}
50
51// pub enum ResizeDimensions{
52//     Exact(usize,usize),
53//     Percentage()
54// }
55/// Resize an image to a new width and height
56/// using the resize method specified
57#[derive(Copy, Clone)]
58pub struct Resize {
59    new_width:  usize,
60    new_height: usize,
61    method:     ResizeMethod
62}
63
64impl Resize {
65    /// Create a new resize operation
66    ///
67    /// # Argument
68    /// - new_width: The new image width
69    /// - new_height: The new image height.
70    /// - method: The resize method to use
71    /// - linearize: Whether or not to convert the image to linear colorspace , resizing is better
72    /// in linear colorspace as it has less artefacts
73    ///
74    #[must_use]
75    pub fn new(new_width: usize, new_height: usize, method: ResizeMethod) -> Resize {
76        Resize {
77            new_width,
78            new_height,
79            method
80        }
81    }
82}
83
84impl OperationsTrait for Resize {
85    fn name(&self) -> &'static str {
86        "Resize"
87    }
88
89    #[allow(clippy::too_many_lines)]
90    #[allow(unused_variables)]
91    fn execute_impl(&self, image: &mut Image) -> Result<(), ImageErrors> {
92        // For any resize always convert gamma to linear.
93        // But check if that is the current image format
94        let is_image_linear = image.metadata().color_trc() == Some(ColorCharacteristics::Linear);
95
96        let transfer_function = image
97            .metadata()
98            .color_trc()
99            .unwrap_or(ColorCharacteristics::sRGB);
100
101        if !is_image_linear {
102            let start = Instant::now();
103            trace!("Converting image to linear along resize method");
104            let transfers = ImageTransfer::new(
105                TransferFunction::from(transfer_function),
106                ConversionType::GammaToLinear
107            );
108            transfers.execute_impl(image)?;
109            let duration = start.elapsed();
110            trace!(
111                "Image conversion to linear successfully completed in {:.2?}",
112                duration
113            );
114        }
115        // if alpha is present premultiply
116        let is_premultiplied = image.metadata().is_premultiplied_alpha();
117        let has_alpha = image.colorspace().has_alpha();
118        let original_depth = image.depth();
119        if !is_premultiplied && has_alpha {
120            // pre multiply looses color if its in u8
121            image.convert_depth(BitDepth::Float32)?;
122            trace!("Premultiplying alpha along resize method");
123            let start = Instant::now();
124            PremultiplyAlpha::new(AlphaState::PreMultiplied).execute_impl(image)?;
125            let duration = start.elapsed();
126            trace!("Premultiply successfully completed in {:.2?}", duration);
127        }
128        let (old_w, old_h) = image.dimensions();
129        let depth = image.depth().bit_type();
130
131        let new_length = self.new_width * self.new_height * image.depth().size_of();
132
133        let precomputed_kernels = if self.method != ResizeMethod::Bilinear {
134            Some(PrecomputedKernels::new(
135                old_w,
136                old_h,
137                self.new_width,
138                self.new_height,
139                self.method
140            ))
141        } else {
142            None
143        };
144
145        let resize_fn = |channel: &mut Channel| -> Result<(), ImageErrors> {
146            let mut new_channel = Channel::new_with_bit_type(new_length, depth);
147            match depth {
148                BitType::U8 => resize::<u8>(
149                    channel.reinterpret_as()?,
150                    new_channel.reinterpret_as_mut()?,
151                    self.method,
152                    old_w,
153                    old_h,
154                    self.new_width,
155                    self.new_height,
156                    precomputed_kernels.as_ref()
157                ),
158                BitType::U16 => resize::<u16>(
159                    channel.reinterpret_as()?,
160                    new_channel.reinterpret_as_mut()?,
161                    self.method,
162                    old_w,
163                    old_h,
164                    self.new_width,
165                    self.new_height,
166                    precomputed_kernels.as_ref()
167                ),
168
169                BitType::F32 => {
170                    resize::<f32>(
171                        channel.reinterpret_as()?,
172                        new_channel.reinterpret_as_mut()?,
173                        self.method,
174                        old_w,
175                        old_h,
176                        self.new_width,
177                        self.new_height,
178                        precomputed_kernels.as_ref()
179                    );
180                }
181                d => return Err(ImageErrors::ImageOperationNotImplemented("resize", d))
182            }
183            *channel = new_channel;
184            Ok(())
185        };
186        execute_on(resize_fn, image, false)?;
187        image.set_dimensions(self.new_width, self.new_height);
188
189        // convert back from premultiplied if we did not get the
190        // image as premultiplied
191        if !is_premultiplied && has_alpha {
192            trace!("Un-premultiplying alpha along resize method");
193            let start = Instant::now();
194            PremultiplyAlpha::new(AlphaState::NonPreMultiplied).execute_impl(image)?;
195            // return the depth originally there
196            image.convert_depth(original_depth)?;
197            let duration = start.elapsed();
198            trace!("Un-premultiply successfully completed in {:.2?}", duration);
199        }
200        // convert back again to gamma
201        if !is_image_linear {
202            let start = Instant::now();
203            trace!("Converting image back to gamma along resize method");
204            let transfers = ImageTransfer::new(
205                TransferFunction::from(transfer_function),
206                ConversionType::LinearToGamma
207            );
208            transfers.execute_impl(image)?;
209            let duration = start.elapsed();
210            trace!(
211                "Image conversion to linear successfully completed in {:.2?}",
212                duration
213            );
214        }
215
216        Ok(())
217    }
218    fn supported_types(&self) -> &'static [BitType] {
219        &[BitType::U8, BitType::U16, BitType::F32]
220    }
221}
222
223/// Return the image resize dimensions that would not cause a distortion
224/// taking into consideration the smaller dimension
225#[must_use]
226#[allow(
227    clippy::cast_precision_loss,
228    clippy::cast_possible_truncation,
229    clippy::cast_sign_loss
230)]
231pub fn ratio_dimensions_smaller(
232    old_w: usize, old_h: usize, new_w: usize, new_h: usize
233) -> (usize, usize) {
234    let ratio_w = old_w as f64 / new_w as f64;
235    let ratio_h = old_h as f64 / new_h as f64;
236    let percent = if ratio_h < ratio_w { ratio_w } else { ratio_h };
237
238    let t = (old_w as f64 / percent) as usize;
239    let u = (old_h as f64 / percent) as usize;
240    (t, u)
241}
242
243/// Return the image resize dimensions that would not cause a distortion
244/// taking into consideration the larger dimension
245#[must_use]
246#[allow(
247    clippy::cast_precision_loss,
248    clippy::cast_possible_truncation,
249    clippy::cast_sign_loss
250)]
251pub fn ratio_dimensions_larger(
252    old_w: usize, old_h: usize, new_w: usize, new_h: usize
253) -> (usize, usize) {
254    let ratio_w = old_w as f64 / new_w as f64;
255    let ratio_h = old_h as f64 / new_h as f64;
256    let percent = if ratio_h < ratio_w { ratio_w } else { ratio_h };
257
258    let t = (old_w as f64 / percent) as usize;
259    let u = (old_h as f64 / percent) as usize;
260    (t, u)
261}
262/// Resize an image **channel** to new dimensions
263///
264/// # Arguments
265/// - in_image: A contiguous slice of a single channel of an image
266/// - out_image: Where we will store the new resized pixels
267/// - method: The resizing method to use
268/// - in_width: `in_image`'s width
269/// - in_height:  `in_image`'s height.
270/// - out_width: The expected width
271/// - out_height: The expected height.
272/// # Panics
273/// - `in_width*in_height` do not match `in_image.len()`.
274/// - `out_width*out_height` do not match `out_image.len()`.
275fn resize<T>(
276    in_image: &[T], out_image: &mut [T], method: ResizeMethod, in_width: usize, in_height: usize,
277    out_width: usize, out_height: usize, precomputed_kernels: Option<&PrecomputedKernels>
278) where
279    T: Copy + NumOps<T> + Default,
280    f32: std::convert::From<T>
281{
282    match method {
283        ResizeMethod::Bilinear => {
284            bilinear::bilinear_impl(
285                in_image, out_image, in_width, in_height, out_width, out_height
286            );
287        }
288
289        _ => match precomputed_kernels {
290            Some(precomputed_kernels) => {
291                seperable_kernel::resample_separable(
292                    in_image,
293                    out_image,
294                    in_width,
295                    in_height,
296                    out_width,
297                    out_height,
298                    precomputed_kernels
299                );
300            }
301            None => {
302                panic!("Precomputed kernels not loaded");
303            }
304        }
305    }
306}
307
308// #[cfg(feature = "benchmarks")]
309// #[cfg(test)]
310// mod benchmarks {
311//     extern crate test;
312//
313//     use crate::resize::{resize, ResizeMethod};
314//
315//     #[bench]
316//     fn bench_resize_linear(b: &mut test::Bencher) {
317//         let width = 4000;
318//         let height = 2000;
319//
320//         let new_width = 1200;
321//         let new_height = 1000;
322//
323//         let dimensions = width * height;
324//
325//         let new_dimensions = new_width * new_height;
326//
327//         let in_vec = vec![255_u16; dimensions];
328//         let mut out_vec = vec![255_u16; new_dimensions];
329//
330//         b.iter(|| {
331//             resize(
332//                 &in_vec,
333//                 &mut out_vec,
334//                 ResizeMethod::Bilinear,
335//                 width,
336//                 height,
337//                 new_width,
338//                 new_height
339//             );
340//         });
341//     }
342//     #[bench]
343//     fn bench_resize_cubic(b: &mut test::Bencher) {
344//         let width = 4000;
345//         let height = 2000;
346//
347//         let new_width = 1500;
348//         let new_height = 1000;
349//
350//         let dimensions = width * height;
351//
352//         let new_dimensions = new_width * new_height;
353//
354//         let in_vec = vec![255_u16; dimensions];
355//         let mut out_vec = vec![255_u16; new_dimensions];
356//
357//         b.iter(|| {
358//             resize(
359//                 &in_vec,
360//                 &mut out_vec,
361//                 ResizeMethod::Bicubic,
362//                 width,
363//                 height,
364//                 new_width,
365//                 new_height
366//             );
367//         });
368//     }
369//     #[bench]
370//     fn bench_resize_lancazos(b: &mut test::Bencher) {
371//         let width = 4000;
372//         let height = 2000;
373//
374//         let new_width = 1500;
375//         let new_height = 1000;
376//
377//         let dimensions = width * height;
378//
379//         let new_dimensions = new_width * new_height;
380//
381//         let in_vec = vec![255_u16; dimensions];
382//         let mut out_vec = vec![255_u16; new_dimensions];
383//
384//         b.iter(|| {
385//             resize(
386//                 &in_vec,
387//                 &mut out_vec,
388//                 ResizeMethod::Lanczos3,
389//                 width,
390//                 height,
391//                 new_width,
392//                 new_height
393//             );
394//         });
395//     }
396// }
397// #[cfg(test)]
398// mod tests {
399//     use crate::resize::{resize, ResizeMethod};
400//
401//     #[test]
402//     fn bench_resize_cubic() {
403//         let width = 4000;
404//         let height = 2000;
405//
406//         let new_width = 1500;
407//         let new_height = 1500;
408//
409//         let dimensions = width * height;
410//
411//         let new_dimensions = new_width * new_height;
412//
413//         let in_vec = vec![255_u16; dimensions];
414//         let mut out_vec = vec![255_u16; new_dimensions];
415//
416//
417//         resize(
418//             &in_vec,
419//             &mut out_vec,
420//             ResizeMethod::Bicubic,
421//             width,
422//             height,
423//             new_width,
424//             new_height
425//         );
426//     }
427//     #[test]
428//     fn test_resize_lancazos() {
429//         let width = 400;
430//         let height = 200;
431//
432//         let new_width = 1500;
433//         let new_height = 1500;
434//
435//         let dimensions = width * height;
436//
437//         let new_dimensions = new_width * new_height;
438//
439//         let in_vec = vec![255_u16; dimensions];
440//         let mut out_vec = vec![255_u16; new_dimensions];
441//
442//         resize(
443//             &in_vec,
444//             &mut out_vec,
445//             ResizeMethod::Lanczos3,
446//             width,
447//             height,
448//             new_width,
449//             new_height
450//         );
451//     }
452// }