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// }