Skip to main content

video_resize/
lib.rs

1#![deny(clippy::all)]
2#![warn(clippy::nursery)]
3#![warn(clippy::pedantic)]
4#![allow(clippy::cast_possible_truncation)]
5#![allow(clippy::cast_possible_wrap)]
6#![allow(clippy::cast_precision_loss)]
7#![allow(clippy::cast_sign_loss)]
8#![allow(clippy::default_trait_access)]
9#![allow(clippy::inconsistent_struct_constructor)]
10#![allow(clippy::inline_always)]
11#![allow(clippy::missing_panics_doc)]
12#![allow(clippy::module_name_repetitions)]
13#![allow(clippy::redundant_closure_for_method_calls)]
14#![allow(clippy::similar_names)]
15#![allow(clippy::struct_excessive_bools)]
16#![allow(clippy::use_self)]
17#![warn(clippy::clone_on_ref_ptr)]
18#![warn(clippy::create_dir)]
19#![warn(clippy::dbg_macro)]
20#![warn(clippy::default_numeric_fallback)]
21#![warn(clippy::exit)]
22#![warn(clippy::filetype_is_file)]
23#![warn(clippy::float_cmp_const)]
24#![warn(clippy::if_then_some_else_none)]
25#![warn(clippy::lossy_float_literal)]
26#![warn(clippy::map_err_ignore)]
27#![warn(clippy::mem_forget)]
28#![warn(clippy::multiple_inherent_impl)]
29#![warn(clippy::pattern_type_mismatch)]
30#![warn(clippy::rc_buffer)]
31#![warn(clippy::rc_mutex)]
32#![warn(clippy::rest_pat_in_fully_bound_structs)]
33#![warn(clippy::same_name_method)]
34#![warn(clippy::str_to_string)]
35#![warn(clippy::undocumented_unsafe_blocks)]
36#![warn(clippy::unnecessary_self_imports)]
37#![warn(clippy::unneeded_field_pattern)]
38#![warn(clippy::use_debug)]
39#![warn(clippy::verbose_file_reads)]
40
41mod resize;
42mod util;
43
44use std::{
45    mem::size_of,
46    num::{NonZeroU8, NonZeroUsize},
47};
48
49pub use crate::resize::{ResizeAlgorithm, ResizeDimensions, algorithms};
50use crate::resize::{resize_horizontal, resize_vertical, should_resize_horiz_first};
51use anyhow::{Result, ensure};
52use v_frame::{
53    chroma::ChromaSubsampling,
54    frame::{Frame, FrameBuilder},
55    pixel::Pixel,
56};
57
58/// Specifies the number of pixels to crop off of each side of the image.
59#[derive(Debug, Clone, Copy)]
60pub struct CropDimensions {
61    pub top: usize,
62    pub bottom: usize,
63    pub left: usize,
64    pub right: usize,
65}
66
67/// Crops a video based on the given crop dimensions.
68///
69/// # Errors
70///
71/// - If the crop dimensions are not even
72pub fn crop<T: Pixel>(input: &Frame<T>, dimensions: CropDimensions) -> Result<Frame<T>> {
73    ensure!(
74        dimensions.top % 2 == 0
75            && dimensions.bottom % 2 == 0
76            && dimensions.left % 2 == 0
77            && dimensions.right % 2 == 0,
78        "Crop dimensions must be a multiple of 2"
79    );
80    ensure!(
81        dimensions.left + dimensions.right <= input.y_plane.width().get() + 4,
82        "Resulting width must be at least 4 pixels"
83    );
84    ensure!(
85        dimensions.top + dimensions.bottom <= input.y_plane.height().get() + 4,
86        "Resulting height must be at least 4 pixels"
87    );
88
89    // SAFETY: checked above
90    let new_w = unsafe {
91        NonZeroUsize::new_unchecked(
92            input.y_plane.width().get() - dimensions.left - dimensions.right,
93        )
94    };
95    // SAFETY: checked above
96    let new_h = unsafe {
97        NonZeroUsize::new_unchecked(
98            input.y_plane.height().get() - dimensions.top - dimensions.bottom,
99        )
100    };
101    let mut output: Frame<T> =
102        FrameBuilder::new(new_w, new_h, input.subsampling, input.bit_depth).build()?;
103    for p in 0..(if input.subsampling == ChromaSubsampling::Monochrome {
104        1
105    } else {
106        3
107    }) {
108        let input_plane = input.plane(p).expect("plane exists");
109        let output_plane = output.plane_mut(p).expect("plane exists");
110        let plane_cfg = &input_plane.geometry();
111        let new_w = new_w.get() / plane_cfg.subsampling_x.get() as usize;
112        let new_h = new_h.get() / plane_cfg.subsampling_y.get() as usize;
113        let left = dimensions.left / plane_cfg.subsampling_x.get() as usize;
114        let top = dimensions.top / plane_cfg.subsampling_y.get() as usize;
115
116        for (out_row, in_row) in output_plane
117            .rows_mut()
118            .zip(input_plane.rows().skip(top).take(new_h))
119        {
120            // SAFETY: `Frame` ensures that certain variants are upheld.
121            // Given that we have verified the crop dimensions do not exceed
122            // the original size of the frame, this is safe and avoids some bounds checks.
123            unsafe {
124                out_row
125                    .as_mut_ptr()
126                    .copy_from_nonoverlapping(in_row.as_ptr().add(left), new_w);
127            }
128        }
129    }
130
131    Ok(output)
132}
133
134#[cfg(feature = "devel")]
135// This function exists for benchmarking and ASM inspection.
136pub fn crop_u8(input: &Frame<u8>, dimensions: CropDimensions) -> Result<Frame<u8>> {
137    crop::<u8>(input, dimensions)
138}
139
140#[cfg(feature = "devel")]
141// This function exists for benchmarking and ASM inspection.
142pub fn crop_u16(input: &Frame<u16>, dimensions: CropDimensions) -> Result<Frame<u16>> {
143    crop::<u16>(input, dimensions)
144}
145
146/// Resizes a video to the given target dimensions.
147///
148/// # Errors
149///
150/// - If the resize dimensions are not even
151/// - If the specified input bit depth does not match the size of `T`
152pub fn resize<T: Pixel, F: ResizeAlgorithm>(
153    input: &Frame<T>,
154    dimensions: ResizeDimensions,
155    input_bit_depth: NonZeroU8,
156) -> Result<Frame<T>> {
157    if size_of::<T>() == 1 {
158        ensure!(
159            input_bit_depth.get() == 8,
160            "input bit depth must be 8 for 8-bit pixel type"
161        );
162    } else if size_of::<T>() == 2 {
163        ensure!(
164            input_bit_depth.get() > 8 && input_bit_depth.get() <= 16,
165            "input bit depth must be between 9-16 for 8-bit pixel type"
166        );
167    } else {
168        unreachable!("32-bit types not implemented in v_frame");
169    }
170    let (ss_x, ss_y) = input
171        .subsampling
172        .subsample_ratio()
173        .map_or((1, 1), |(x, y)| (x.get() as usize, y.get() as usize));
174    ensure!(
175        dimensions.width.get() % ss_x == 0 && dimensions.height.get() % ss_y == 0,
176        "Resize dimensions must be a multiple of subsampling ratio"
177    );
178    ensure!(
179        dimensions.width.get() >= 4 && dimensions.height.get() >= 4,
180        "Resulting image must be at least 4x4 pixels"
181    );
182
183    let src_w = input.y_plane.width();
184    let src_h = input.y_plane.height();
185    let resize_horiz = src_w != dimensions.width;
186    let resize_vert = src_h != dimensions.height;
187    if !resize_horiz {
188        return resize_vertical::<T, F>(input, dimensions.height, input_bit_depth);
189    }
190
191    if !resize_vert {
192        return resize_horizontal::<T, F>(input, dimensions.width, input_bit_depth);
193    }
194
195    let horiz_first = should_resize_horiz_first(
196        dimensions.width.get() as f32 / src_w.get() as f32,
197        dimensions.height.get() as f32 / src_h.get() as f32,
198    );
199    if horiz_first {
200        let temp = resize_horizontal::<T, F>(input, dimensions.width, input_bit_depth)?;
201        return resize_vertical::<T, F>(&temp, dimensions.height, input_bit_depth);
202    }
203    let temp = resize_vertical::<T, F>(input, dimensions.height, input_bit_depth)?;
204    resize_horizontal::<T, F>(&temp, dimensions.width, input_bit_depth)
205}
206
207#[cfg(feature = "devel")]
208pub fn resize_horizontal_u8_bicubic(input: &Frame<u8>, dest_width: usize) -> Frame<u8> {
209    use resize::algorithms::BicubicMitchell;
210
211    resize_horizontal::<u8, BicubicMitchell>(input, dest_width, 8)
212}
213
214#[cfg(feature = "devel")]
215pub fn resize_horizontal_u16_bicubic(
216    input: &Frame<u16>,
217    dest_width: usize,
218    input_bit_depth: usize,
219) -> Frame<u16> {
220    use resize::algorithms::BicubicMitchell;
221
222    resize_horizontal::<u16, BicubicMitchell>(input, dest_width, input_bit_depth)
223}
224
225#[cfg(feature = "devel")]
226pub fn resize_vertical_u8_bicubic(input: &Frame<u8>, dest_height: usize) -> Frame<u8> {
227    use resize::algorithms::BicubicMitchell;
228
229    resize_vertical::<u8, BicubicMitchell>(input, dest_height, 8)
230}
231
232#[cfg(feature = "devel")]
233pub fn resize_vertical_u16_bicubic(
234    input: &Frame<u16>,
235    dest_height: usize,
236    input_bit_depth: usize,
237) -> Frame<u16> {
238    use resize::algorithms::BicubicMitchell;
239
240    resize_vertical::<u16, BicubicMitchell>(input, dest_height, input_bit_depth)
241}
242
243#[cfg(feature = "devel")]
244pub fn resize_horizontal_u8_lanczos3(input: &Frame<u8>, dest_width: usize) -> Frame<u8> {
245    use resize::algorithms::Lanczos3;
246
247    resize_horizontal::<u8, Lanczos3>(input, dest_width, 8)
248}
249
250#[cfg(feature = "devel")]
251pub fn resize_horizontal_u16_lanczos3(
252    input: &Frame<u16>,
253    dest_width: usize,
254    input_bit_depth: usize,
255) -> Frame<u16> {
256    use resize::algorithms::Lanczos3;
257
258    resize_horizontal::<u16, Lanczos3>(input, dest_width, input_bit_depth)
259}
260
261#[cfg(feature = "devel")]
262pub fn resize_vertical_u8_lanczos3(input: &Frame<u8>, dest_height: usize) -> Frame<u8> {
263    use resize::algorithms::Lanczos3;
264
265    resize_vertical::<u8, Lanczos3>(input, dest_height, 8)
266}
267
268#[cfg(feature = "devel")]
269pub fn resize_vertical_u16_lanczos3(
270    input: &Frame<u16>,
271    dest_height: usize,
272    input_bit_depth: usize,
273) -> Frame<u16> {
274    use resize::algorithms::Lanczos3;
275
276    resize_vertical::<u16, Lanczos3>(input, dest_height, input_bit_depth)
277}
278
279#[cfg(feature = "devel")]
280pub fn resize_horizontal_u8_spline36(input: &Frame<u8>, dest_width: usize) -> Frame<u8> {
281    use resize::algorithms::Spline36;
282
283    resize_horizontal::<u8, Spline36>(input, dest_width, 8)
284}
285
286#[cfg(feature = "devel")]
287pub fn resize_horizontal_u16_spline36(
288    input: &Frame<u16>,
289    dest_width: usize,
290    input_bit_depth: usize,
291) -> Frame<u16> {
292    use resize::algorithms::Spline36;
293
294    resize_horizontal::<u16, Spline36>(input, dest_width, input_bit_depth)
295}
296
297#[cfg(feature = "devel")]
298pub fn resize_vertical_u8_spline36(input: &Frame<u8>, dest_height: usize) -> Frame<u8> {
299    use resize::algorithms::Spline36;
300
301    resize_vertical::<u8, Spline36>(input, dest_height, 8)
302}
303
304#[cfg(feature = "devel")]
305pub fn resize_vertical_u16_spline36(
306    input: &Frame<u16>,
307    dest_height: usize,
308    input_bit_depth: usize,
309) -> Frame<u16> {
310    use resize::algorithms::Spline36;
311
312    resize_vertical::<u16, Spline36>(input, dest_height, input_bit_depth)
313}
314
315/// Resamples a video to the given bit depth.
316///
317/// # Errors
318///
319/// - If an unsupported target bit depth is chosen.
320///   - Currently supported bit depths are 8, 10, 12, and 16.
321/// - If the specified input bit depth does not match the size of `T`
322/// - If the specified target bit depth does not match the size of `U`
323///
324/// # Panics
325///
326/// - Not yet implemented
327#[doc(hidden)]
328pub fn resample_bit_depth<T: Pixel, U: Pixel>(
329    _input: &Frame<T>,
330    input_bit_depth: usize,
331    target_bit_depth: usize,
332    _dither: bool,
333) -> Result<Frame<U>> {
334    if size_of::<T>() == 1 {
335        ensure!(
336            input_bit_depth == 8,
337            "input bit depth must be 8 for 8-bit pixel type"
338        );
339    } else if size_of::<T>() == 2 {
340        ensure!(
341            input_bit_depth > 8 && input_bit_depth <= 16,
342            "input bit depth must be between 9-16 for 8-bit pixel type"
343        );
344    } else {
345        unreachable!("32-bit types not implemented in v_frame");
346    }
347    if size_of::<U>() == 1 {
348        ensure!(
349            target_bit_depth == 8,
350            "target bit depth must be 8 for 8-bit pixel type"
351        );
352    } else if size_of::<U>() == 2 {
353        ensure!(
354            target_bit_depth > 8 && target_bit_depth <= 16,
355            "target bit depth must be between 9-16 for 8-bit pixel type"
356        );
357    } else {
358        unreachable!("32-bit types not implemented in v_frame");
359    }
360    ensure!(
361        [8, 10, 12, 16].contains(&target_bit_depth),
362        "Currently supported bit depths are 8, 10, 12, and 16"
363    );
364
365    todo!()
366}
367
368/// Resamples a video to the given chroma subsampling.
369///
370/// # Errors
371///
372/// - If the specified input bit depth does not match the size of `T`
373///
374/// # Panics
375///
376/// - Not yet implemented
377#[doc(hidden)]
378pub fn resample_chroma_sampling<T: Pixel, F: ResizeAlgorithm>(
379    _input: &Frame<T>,
380    input_bit_depth: usize,
381    _target_chroma_sampling: ChromaSubsampling,
382) -> Result<Frame<T>> {
383    if size_of::<T>() == 1 {
384        ensure!(
385            input_bit_depth == 8,
386            "input bit depth must be 8 for 8-bit pixel type"
387        );
388    } else if size_of::<T>() == 2 {
389        ensure!(
390            input_bit_depth > 8 && input_bit_depth <= 16,
391            "input bit depth must be between 9-16 for 8-bit pixel type"
392        );
393    } else {
394        unreachable!("32-bit types not implemented in v_frame");
395    }
396
397    todo!()
398}
399
400#[cfg(test)]
401mod tests {
402    use std::{
403        fs,
404        num::{NonZeroU8, NonZeroUsize},
405        path::Path,
406    };
407
408    use image::{DynamicImage, Rgb32FImage};
409    use yuvxyb::{
410        ColorPrimaries, Frame, MatrixCoefficients, Rgb, TransferCharacteristic, Yuv, YuvConfig,
411    };
412
413    use crate::{
414        CropDimensions, ResizeDimensions,
415        algorithms::{BicubicCatmullRom, Bilinear, Lanczos3, Point, Spline64},
416        crop, resize,
417    };
418
419    fn get_u8_test_image() -> Frame<u8> {
420        let i = image::open("./test_files/ducks_take_off.png")
421            .unwrap()
422            .to_rgb32f();
423        let rgb = Rgb::new(
424            i.pixels().map(|p| [p[0], p[1], p[2]]).collect(),
425            NonZeroUsize::new(i.width() as usize).expect("not zero"),
426            NonZeroUsize::new(i.height() as usize).expect("not zero"),
427            TransferCharacteristic::BT1886,
428            ColorPrimaries::BT709,
429        )
430        .unwrap();
431        let yuv: Yuv<u8> = (
432            rgb,
433            YuvConfig {
434                bit_depth: 8,
435                subsampling_x: 1,
436                subsampling_y: 1,
437                full_range: false,
438                matrix_coefficients: MatrixCoefficients::BT709,
439                transfer_characteristics: TransferCharacteristic::BT1886,
440                color_primaries: ColorPrimaries::BT709,
441            },
442        )
443            .try_into()
444            .unwrap();
445        let data = yuv.data();
446        data.clone()
447    }
448
449    fn get_u16_test_image() -> Frame<u16> {
450        let i = image::open("./test_files/ducks_take_off.png")
451            .unwrap()
452            .to_rgb32f();
453        let rgb = Rgb::new(
454            i.pixels().map(|p| [p[0], p[1], p[2]]).collect(),
455            NonZeroUsize::new(i.width() as usize).expect("not zero"),
456            NonZeroUsize::new(i.height() as usize).expect("not zero"),
457            TransferCharacteristic::BT1886,
458            ColorPrimaries::BT709,
459        )
460        .unwrap();
461        let yuv: Yuv<u16> = (
462            rgb,
463            YuvConfig {
464                bit_depth: 10,
465                subsampling_x: 1,
466                subsampling_y: 1,
467                full_range: false,
468                matrix_coefficients: MatrixCoefficients::BT709,
469                transfer_characteristics: TransferCharacteristic::BT1886,
470                color_primaries: ColorPrimaries::BT709,
471            },
472        )
473            .try_into()
474            .unwrap();
475        let data = yuv.data();
476        data.clone()
477    }
478
479    fn output_image_from_u8(image: Frame<u8>, filename: &str) {
480        let width = image.y_plane.width();
481        let height = image.y_plane.height();
482        let yuv: Yuv<u8> = Yuv::new(
483            image,
484            YuvConfig {
485                bit_depth: 8,
486                subsampling_x: 1,
487                subsampling_y: 1,
488                full_range: false,
489                matrix_coefficients: MatrixCoefficients::BT709,
490                transfer_characteristics: TransferCharacteristic::BT1886,
491                color_primaries: ColorPrimaries::BT709,
492            },
493        )
494        .unwrap();
495        let rgb: Rgb = yuv.try_into().unwrap();
496        let i = DynamicImage::ImageRgb32F(
497            Rgb32FImage::from_vec(
498                width.get() as u32,
499                height.get() as u32,
500                rgb.data().iter().copied().flatten().collect(),
501            )
502            .unwrap(),
503        )
504        .to_rgb8();
505        if !Path::new("/tmp/video-resize-tests").is_dir() {
506            fs::create_dir_all("/tmp/video-resize-tests").unwrap();
507        }
508        i.save(filename).unwrap();
509    }
510
511    fn output_image_from_u16(image: Frame<u16>, filename: &str) {
512        let width = image.y_plane.width();
513        let height = image.y_plane.height();
514        let yuv: Yuv<u16> = Yuv::new(
515            image,
516            YuvConfig {
517                bit_depth: 10,
518                subsampling_x: 1,
519                subsampling_y: 1,
520                full_range: false,
521                matrix_coefficients: MatrixCoefficients::BT709,
522                transfer_characteristics: TransferCharacteristic::BT1886,
523                color_primaries: ColorPrimaries::BT709,
524            },
525        )
526        .unwrap();
527        let rgb: Rgb = yuv.try_into().unwrap();
528        let i = DynamicImage::ImageRgb32F(
529            Rgb32FImage::from_vec(
530                width.get() as u32,
531                height.get() as u32,
532                rgb.data().iter().copied().flatten().collect(),
533            )
534            .unwrap(),
535        )
536        .to_rgb16();
537        if !Path::new("/tmp/video-resize-tests").is_dir() {
538            fs::create_dir_all("/tmp/video-resize-tests").unwrap();
539        }
540        i.save(filename).unwrap();
541    }
542
543    #[test]
544    fn crop_u8() {
545        let input = get_u8_test_image();
546        let output = crop::<u8>(
547            &input,
548            CropDimensions {
549                top: 40,
550                bottom: 40,
551                left: 20,
552                right: 20,
553            },
554        )
555        .unwrap();
556        output_image_from_u8(output, "/tmp/video-resize-tests/crop_u8.png");
557    }
558
559    #[test]
560    fn crop_u16() {
561        let input = get_u16_test_image();
562        let output = crop::<u16>(
563            &input,
564            CropDimensions {
565                top: 40,
566                bottom: 40,
567                left: 20,
568                right: 20,
569            },
570        )
571        .unwrap();
572        output_image_from_u16(output, "/tmp/video-resize-tests/crop_u16.png");
573    }
574
575    #[test]
576    fn resize_point_u8_down() {
577        let input = get_u8_test_image();
578        let output = resize::<u8, Point>(
579            &input,
580            ResizeDimensions {
581                width: NonZeroUsize::new(1280).expect("not zero"),
582                height: NonZeroUsize::new(720).expect("not zero"),
583            },
584            NonZeroU8::new(8).expect("not zero"),
585        )
586        .unwrap();
587        output_image_from_u8(output, "/tmp/video-resize-tests/resize_point_u8_down.png");
588    }
589
590    #[test]
591    fn resize_point_u16_down() {
592        let input = get_u16_test_image();
593        let output = resize::<u16, Point>(
594            &input,
595            ResizeDimensions {
596                width: NonZeroUsize::new(1280).expect("not zero"),
597                height: NonZeroUsize::new(720).expect("not zero"),
598            },
599            NonZeroU8::new(10).expect("not zero"),
600        )
601        .unwrap();
602        output_image_from_u16(output, "/tmp/video-resize-tests/resize_point_u16_down.png");
603    }
604
605    #[test]
606    fn resize_point_u8_up() {
607        let input = get_u8_test_image();
608        let output = resize::<u8, Point>(
609            &input,
610            ResizeDimensions {
611                width: NonZeroUsize::new(2560).expect("not zero"),
612                height: NonZeroUsize::new(1440).expect("not zero"),
613            },
614            NonZeroU8::new(8).expect("not zero"),
615        )
616        .unwrap();
617        output_image_from_u8(output, "/tmp/video-resize-tests/resize_point_u8_up.png");
618    }
619
620    #[test]
621    fn resize_point_u16_up() {
622        let input = get_u16_test_image();
623        let output = resize::<u16, Point>(
624            &input,
625            ResizeDimensions {
626                width: NonZeroUsize::new(2560).expect("not zero"),
627                height: NonZeroUsize::new(1440).expect("not zero"),
628            },
629            NonZeroU8::new(10).expect("not zero"),
630        )
631        .unwrap();
632        output_image_from_u16(output, "/tmp/video-resize-tests/resize_point_u16_up.png");
633    }
634
635    #[test]
636    fn resize_bilinear_u8_down() {
637        let input = get_u8_test_image();
638        let output = resize::<u8, Bilinear>(
639            &input,
640            ResizeDimensions {
641                width: NonZeroUsize::new(1280).expect("not zero"),
642                height: NonZeroUsize::new(720).expect("not zero"),
643            },
644            NonZeroU8::new(8).expect("not zero"),
645        )
646        .unwrap();
647        output_image_from_u8(
648            output,
649            "/tmp/video-resize-tests/resize_bilinear_u8_down.png",
650        );
651    }
652
653    #[test]
654    fn resize_bilinear_u16_down() {
655        let input = get_u16_test_image();
656        let output = resize::<u16, Bilinear>(
657            &input,
658            ResizeDimensions {
659                width: NonZeroUsize::new(1280).expect("not zero"),
660                height: NonZeroUsize::new(720).expect("not zero"),
661            },
662            NonZeroU8::new(10).expect("not zero"),
663        )
664        .unwrap();
665        output_image_from_u16(
666            output,
667            "/tmp/video-resize-tests/resize_bilinear_u16_down.png",
668        );
669    }
670
671    #[test]
672    fn resize_bilinear_u8_up() {
673        let input = get_u8_test_image();
674        let output = resize::<u8, Bilinear>(
675            &input,
676            ResizeDimensions {
677                width: NonZeroUsize::new(2560).expect("not zero"),
678                height: NonZeroUsize::new(1440).expect("not zero"),
679            },
680            NonZeroU8::new(8).expect("not zero"),
681        )
682        .unwrap();
683        output_image_from_u8(output, "/tmp/video-resize-tests/resize_bilinear_u8_up.png");
684    }
685
686    #[test]
687    fn resize_bilinear_u16_up() {
688        let input = get_u16_test_image();
689        let output = resize::<u16, Bilinear>(
690            &input,
691            ResizeDimensions {
692                width: NonZeroUsize::new(2560).expect("not zero"),
693                height: NonZeroUsize::new(1440).expect("not zero"),
694            },
695            NonZeroU8::new(10).expect("not zero"),
696        )
697        .unwrap();
698        output_image_from_u16(output, "/tmp/video-resize-tests/resize_bilinear_u16_up.png");
699    }
700
701    #[test]
702    fn resize_bicubic_u8_down() {
703        let input = get_u8_test_image();
704        let output = resize::<u8, BicubicCatmullRom>(
705            &input,
706            ResizeDimensions {
707                width: NonZeroUsize::new(1280).expect("not zero"),
708                height: NonZeroUsize::new(720).expect("not zero"),
709            },
710            NonZeroU8::new(8).expect("not zero"),
711        )
712        .unwrap();
713        output_image_from_u8(output, "/tmp/video-resize-tests/resize_bicubic_u8_down.png");
714    }
715
716    #[test]
717    fn resize_bicubic_u16_down() {
718        let input = get_u16_test_image();
719        let output = resize::<u16, BicubicCatmullRom>(
720            &input,
721            ResizeDimensions {
722                width: NonZeroUsize::new(1280).expect("not zero"),
723                height: NonZeroUsize::new(720).expect("not zero"),
724            },
725            NonZeroU8::new(10).expect("not zero"),
726        )
727        .unwrap();
728        output_image_from_u16(
729            output,
730            "/tmp/video-resize-tests/resize_bicubic_u16_down.png",
731        );
732    }
733
734    #[test]
735    fn resize_bicubic_u8_up() {
736        let input = get_u8_test_image();
737        let output = resize::<u8, BicubicCatmullRom>(
738            &input,
739            ResizeDimensions {
740                width: NonZeroUsize::new(2560).expect("not zero"),
741                height: NonZeroUsize::new(1440).expect("not zero"),
742            },
743            NonZeroU8::new(8).expect("not zero"),
744        )
745        .unwrap();
746        output_image_from_u8(output, "/tmp/video-resize-tests/resize_bicubic_u8_up.png");
747    }
748
749    #[test]
750    fn resize_bicubic_u16_up() {
751        let input = get_u16_test_image();
752        let output = resize::<u16, BicubicCatmullRom>(
753            &input,
754            ResizeDimensions {
755                width: NonZeroUsize::new(2560).expect("not zero"),
756                height: NonZeroUsize::new(1440).expect("not zero"),
757            },
758            NonZeroU8::new(10).expect("not zero"),
759        )
760        .unwrap();
761        output_image_from_u16(output, "/tmp/video-resize-tests/resize_bicubic_u16_up.png");
762    }
763
764    #[test]
765    fn resize_lanczos_u8_down() {
766        let input = get_u8_test_image();
767        let output = resize::<u8, Lanczos3>(
768            &input,
769            ResizeDimensions {
770                width: NonZeroUsize::new(1280).expect("not zero"),
771                height: NonZeroUsize::new(720).expect("not zero"),
772            },
773            NonZeroU8::new(8).expect("not zero"),
774        )
775        .unwrap();
776        output_image_from_u8(output, "/tmp/video-resize-tests/resize_lanczos_u8_down.png");
777    }
778
779    #[test]
780    fn resize_lanczos_u16_down() {
781        let input = get_u16_test_image();
782        let output = resize::<u16, Lanczos3>(
783            &input,
784            ResizeDimensions {
785                width: NonZeroUsize::new(1280).expect("not zero"),
786                height: NonZeroUsize::new(720).expect("not zero"),
787            },
788            NonZeroU8::new(10).expect("not zero"),
789        )
790        .unwrap();
791        output_image_from_u16(
792            output,
793            "/tmp/video-resize-tests/resize_lanczos_u16_down.png",
794        );
795    }
796
797    #[test]
798    fn resize_lanczos_u8_up() {
799        let input = get_u8_test_image();
800        let output = resize::<u8, Lanczos3>(
801            &input,
802            ResizeDimensions {
803                width: NonZeroUsize::new(2560).expect("not zero"),
804                height: NonZeroUsize::new(1440).expect("not zero"),
805            },
806            NonZeroU8::new(8).expect("not zero"),
807        )
808        .unwrap();
809        output_image_from_u8(output, "/tmp/video-resize-tests/resize_lanczos_u8_up.png");
810    }
811
812    #[test]
813    fn resize_lanczos_u16_up() {
814        let input = get_u16_test_image();
815        let output = resize::<u16, Lanczos3>(
816            &input,
817            ResizeDimensions {
818                width: NonZeroUsize::new(2560).expect("not zero"),
819                height: NonZeroUsize::new(1440).expect("not zero"),
820            },
821            NonZeroU8::new(10).expect("not zero"),
822        )
823        .unwrap();
824        output_image_from_u16(output, "/tmp/video-resize-tests/resize_lanczos_u16_up.png");
825    }
826
827    #[test]
828    fn resize_spline64_u8_down() {
829        let input = get_u8_test_image();
830        let output = resize::<u8, Spline64>(
831            &input,
832            ResizeDimensions {
833                width: NonZeroUsize::new(1280).expect("not zero"),
834                height: NonZeroUsize::new(720).expect("not zero"),
835            },
836            NonZeroU8::new(8).expect("not zero"),
837        )
838        .unwrap();
839        output_image_from_u8(
840            output,
841            "/tmp/video-resize-tests/resize_spline64_u8_down.png",
842        );
843    }
844
845    #[test]
846    fn resize_spline64_u16_down() {
847        let input = get_u16_test_image();
848        let output = resize::<u16, Spline64>(
849            &input,
850            ResizeDimensions {
851                width: NonZeroUsize::new(1280).expect("not zero"),
852                height: NonZeroUsize::new(720).expect("not zero"),
853            },
854            NonZeroU8::new(10).expect("not zero"),
855        )
856        .unwrap();
857        output_image_from_u16(
858            output,
859            "/tmp/video-resize-tests/resize_spline64_u16_down.png",
860        );
861    }
862
863    #[test]
864    fn resize_spline64_u8_up() {
865        let input = get_u8_test_image();
866        let output = resize::<u8, Spline64>(
867            &input,
868            ResizeDimensions {
869                width: NonZeroUsize::new(2560).expect("not zero"),
870                height: NonZeroUsize::new(1440).expect("not zero"),
871            },
872            NonZeroU8::new(8).expect("not zero"),
873        )
874        .unwrap();
875        output_image_from_u8(output, "/tmp/video-resize-tests/resize_spline64_u8_up.png");
876    }
877
878    #[test]
879    fn resize_spline64_u16_up() {
880        let input = get_u16_test_image();
881        let output = resize::<u16, Spline64>(
882            &input,
883            ResizeDimensions {
884                width: NonZeroUsize::new(2560).expect("not zero"),
885                height: NonZeroUsize::new(1440).expect("not zero"),
886            },
887            NonZeroU8::new(10).expect("not zero"),
888        )
889        .unwrap();
890        output_image_from_u16(output, "/tmp/video-resize-tests/resize_spline64_u16_up.png");
891    }
892}