video_rs/
resize.rs

1/// Represents width and height in a tuple.
2type Dims = (u32, u32);
3
4/// Represents the possible resize strategies.
5#[derive(Debug, Copy, Clone, PartialEq, Eq)]
6pub enum Resize {
7    /// When resizing with `Resize::Exact`, each frame will be resized to the exact width and height
8    /// given, without taking into account aspect ratio.
9    Exact(u32, u32),
10    /// When resizing with `Resize::Fit`, each frame will be resized to the biggest width and height
11    /// possible within the given dimensions, without changing the aspect ratio.
12    Fit(u32, u32),
13    /// When resizing with `Resize::FitEven`, each frame will be resized to the biggest even width
14    /// and height possible within the given dimensions, maintaining aspect ratio. Resizing using
15    /// this method can fail if there exist no dimensions that fit these constraints.
16    ///
17    /// Note that this resizing method is especially useful since some encoders only accept frames
18    /// with dimensions that are divisible by 2.
19    FitEven(u32, u32),
20}
21
22impl Resize {
23    /// Compute the dimensions after resizing depending on the resize strategy.
24    ///
25    /// # Arguments
26    ///
27    /// * `dims` - Input dimensions (width and height).
28    ///
29    /// # Return value
30    ///
31    /// Tuple of width and height with dimensions after resizing.
32    pub fn compute_for(self, dims: Dims) -> Option<Dims> {
33        match self {
34            Resize::Exact(w, h) => Some((w, h)),
35            Resize::Fit(w, h) => calculate_fit_dims(dims, (w, h)),
36            Resize::FitEven(w, h) => calculate_fit_dims_even(dims, (w, h)),
37        }
38    }
39}
40
41/// Calculates the maximum image dimensions `w` and `h` that fit inside `w_max` and `h_max`
42/// retaining the original aspect ratio.
43///
44/// # Arguments
45///
46/// * `dims` - Original dimensions: width and height.
47/// * `fit_dims` - Dimensions to fit in: width and height.
48///
49/// # Return value
50///
51/// The fitted dimensions if they exist and are positive and more than zero.
52fn calculate_fit_dims(dims: (u32, u32), fit_dims: (u32, u32)) -> Option<(u32, u32)> {
53    let (w, h) = dims;
54    let (w_max, h_max) = fit_dims;
55    if w_max >= w && h_max >= h {
56        Some((w, h))
57    } else {
58        let wf = w_max as f32 / w as f32;
59        let hf = h_max as f32 / h as f32;
60        let f = wf.min(hf);
61        let (w_out, h_out) = ((w as f32 * f) as u32, (h as f32 * f) as u32);
62        if (w_out > 0) && (h_out > 0) {
63            Some((w_out, h_out))
64        } else {
65            None
66        }
67    }
68}
69
70/// Calculates the maximum image dimensions `w` and `h` that fit inside `w_max` and `h_max`
71/// retaining the original aspect ratio, where both the width and height must be divisble by two.
72///
73/// Note that this method will even reduce the dimensions to even width and height if they already
74/// fit in `fit_dims`.
75///
76/// # Arguments
77///
78/// * `dims` - Original dimensions: width and height.
79/// * `fit_dims` - Dimensions to fit in: width and height.
80///
81/// # Return value
82///
83/// The fitted dimensions if they exist and are positive and more than zero.
84fn calculate_fit_dims_even(dims: (u32, u32), fit_dims: (u32, u32)) -> Option<(u32, u32)> {
85    let (w, h) = dims;
86    let (mut w_max, mut h_max) = fit_dims;
87    while w_max > 0 && h_max > 0 {
88        let wf = w_max as f32 / w as f32;
89        let hf = h_max as f32 / h as f32;
90        let f = wf.min(hf).min(1.0);
91        let out_w = (w as f32 * f).round() as u32;
92        let out_h = (h as f32 * f).round() as u32;
93        if (out_w > 0) && (out_h > 0) {
94            if (out_w % 2 == 0) && (out_h % 2 == 0) {
95                return Some((out_w, out_h));
96            } else if wf < hf {
97                w_max -= 1;
98            } else {
99                h_max -= 1;
100            }
101        } else {
102            break;
103        }
104    }
105    None
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    const TESTING_DIM_CANDIDATES: [u32; 8] = [0, 1, 2, 3, 8, 111, 256, 1000];
113
114    #[test]
115    fn calculate_fit_dims_works() {
116        let testset = generate_testset();
117        for ((w, h), (fit_w, fit_h)) in testset {
118            let out = calculate_fit_dims((w, h), (fit_w, fit_h));
119            if let Some((out_w, out_h)) = out {
120                let input_dim_zero = w == 0 || h == 0 || fit_w == 0 || fit_h == 0;
121                let output_dim_zero = out_w == 0 || out_h == 0;
122                assert!(
123                    (input_dim_zero && output_dim_zero) || (!input_dim_zero && !output_dim_zero),
124                    "computed dims are never zero unless the inputs dims were",
125                );
126                assert!(
127                    (out_w <= fit_w) && (out_h <= fit_h),
128                    "computed dims fit inside provided dims",
129                );
130            }
131        }
132    }
133
134    #[test]
135    fn calculate_fit_dims_even_works() {
136        let testset = generate_testset();
137        for ((w, h), (fit_w, fit_h)) in testset {
138            let out = calculate_fit_dims_even((w, h), (fit_w, fit_h));
139            if let Some((out_w, out_h)) = out {
140                let input_dim_zero = w == 0 || h == 0 || fit_w == 0 || fit_h == 0;
141                let output_dim_zero = out_w == 0 || out_h == 0;
142                assert!(
143                    (input_dim_zero && output_dim_zero) || (!input_dim_zero && !output_dim_zero),
144                    "computed dims are never zero unless the inputs dims were",
145                );
146                assert!(
147                    (out_w % 2 == 0) && (out_h % 2 == 0),
148                    "computed dims are even",
149                );
150                assert!(
151                    (out_w <= fit_w) && (out_h <= fit_h),
152                    "computed dims fit inside provided dims",
153                );
154            }
155        }
156    }
157
158    fn generate_testset() -> Vec<((u32, u32), (u32, u32))> {
159        let testing_dims = generate_testing_dims();
160        testing_dims
161            .iter()
162            .flat_map(|dims| testing_dims.iter().map(|fit_dims| (*dims, *fit_dims)))
163            .collect()
164    }
165
166    fn generate_testing_dims() -> Vec<(u32, u32)> {
167        TESTING_DIM_CANDIDATES
168            .iter()
169            .flat_map(|a| TESTING_DIM_CANDIDATES.iter().map(|b| (*a, *b)))
170            .collect()
171    }
172}