/src/image/src/math/utils.rs
Line | Count | Source (jump to first uncovered line) |
1 | | //! Shared mathematical utility functions. |
2 | | |
3 | | use std::cmp::max; |
4 | | |
5 | | /// Calculates the width and height an image should be resized to. |
6 | | /// This preserves aspect ratio, and based on the `fill` parameter |
7 | | /// will either fill the dimensions to fit inside the smaller constraint |
8 | | /// (will overflow the specified bounds on one axis to preserve |
9 | | /// aspect ratio), or will shrink so that both dimensions are |
10 | | /// completely contained within the given `width` and `height`, |
11 | | /// with empty space on one axis. |
12 | 0 | pub(crate) fn resize_dimensions( |
13 | 0 | width: u32, |
14 | 0 | height: u32, |
15 | 0 | nwidth: u32, |
16 | 0 | nheight: u32, |
17 | 0 | fill: bool, |
18 | 0 | ) -> (u32, u32) { |
19 | 0 | let wratio = f64::from(nwidth) / f64::from(width); |
20 | 0 | let hratio = f64::from(nheight) / f64::from(height); |
21 | | |
22 | 0 | let ratio = if fill { |
23 | 0 | f64::max(wratio, hratio) |
24 | | } else { |
25 | 0 | f64::min(wratio, hratio) |
26 | | }; |
27 | | |
28 | 0 | let nw = max((f64::from(width) * ratio).round() as u64, 1); |
29 | 0 | let nh = max((f64::from(height) * ratio).round() as u64, 1); |
30 | 0 |
|
31 | 0 | if nw > u64::from(u32::MAX) { |
32 | 0 | let ratio = f64::from(u32::MAX) / f64::from(width); |
33 | 0 | (u32::MAX, max((f64::from(height) * ratio).round() as u32, 1)) |
34 | 0 | } else if nh > u64::from(u32::MAX) { |
35 | 0 | let ratio = f64::from(u32::MAX) / f64::from(height); |
36 | 0 | (max((f64::from(width) * ratio).round() as u32, 1), u32::MAX) |
37 | | } else { |
38 | 0 | (nw as u32, nh as u32) |
39 | | } |
40 | 0 | } |
41 | | |
42 | | #[cfg(test)] |
43 | | mod test { |
44 | | quickcheck! { |
45 | | fn resize_bounds_correctly_width(old_w: u32, new_w: u32) -> bool { |
46 | | if old_w == 0 || new_w == 0 { return true; } |
47 | | // In this case, the scaling is limited by scaling of height. |
48 | | // We could check that case separately but it does not conform to the same expectation. |
49 | | if u64::from(new_w) * 400u64 >= u64::from(old_w) * u64::from(u32::MAX) { return true; } |
50 | | |
51 | | let result = super::resize_dimensions(old_w, 400, new_w, u32::MAX, false); |
52 | | let exact = (400_f64 * f64::from(new_w) / f64::from(old_w)).round() as u32; |
53 | | result.0 == new_w && result.1 == exact.max(1) |
54 | | } |
55 | | } |
56 | | |
57 | | quickcheck! { |
58 | | fn resize_bounds_correctly_height(old_h: u32, new_h: u32) -> bool { |
59 | | if old_h == 0 || new_h == 0 { return true; } |
60 | | // In this case, the scaling is limited by scaling of width. |
61 | | // We could check that case separately but it does not conform to the same expectation. |
62 | | if 400u64 * u64::from(new_h) >= u64::from(old_h) * u64::from(u32::MAX) { return true; } |
63 | | |
64 | | let result = super::resize_dimensions(400, old_h, u32::MAX, new_h, false); |
65 | | let exact = (400_f64 * f64::from(new_h) / f64::from(old_h)).round() as u32; |
66 | | result.1 == new_h && result.0 == exact.max(1) |
67 | | } |
68 | | } |
69 | | |
70 | | #[test] |
71 | | fn resize_handles_fill() { |
72 | | let result = super::resize_dimensions(100, 200, 200, 500, true); |
73 | | assert!(result.0 == 250); |
74 | | assert!(result.1 == 500); |
75 | | |
76 | | let result = super::resize_dimensions(200, 100, 500, 200, true); |
77 | | assert!(result.0 == 500); |
78 | | assert!(result.1 == 250); |
79 | | } |
80 | | |
81 | | #[test] |
82 | | fn resize_never_rounds_to_zero() { |
83 | | let result = super::resize_dimensions(1, 150, 128, 128, false); |
84 | | assert!(result.0 > 0); |
85 | | assert!(result.1 > 0); |
86 | | } |
87 | | |
88 | | #[test] |
89 | | fn resize_handles_overflow() { |
90 | | let result = super::resize_dimensions(100, u32::MAX, 200, u32::MAX, true); |
91 | | assert!(result.0 == 100); |
92 | | assert!(result.1 == u32::MAX); |
93 | | |
94 | | let result = super::resize_dimensions(u32::MAX, 100, u32::MAX, 200, true); |
95 | | assert!(result.0 == u32::MAX); |
96 | | assert!(result.1 == 100); |
97 | | } |
98 | | |
99 | | #[test] |
100 | | fn resize_rounds() { |
101 | | // Only truncation will result in (3840, 2229) and (2160, 3719) |
102 | | let result = super::resize_dimensions(4264, 2476, 3840, 2160, true); |
103 | | assert_eq!(result, (3840, 2230)); |
104 | | |
105 | | let result = super::resize_dimensions(2476, 4264, 2160, 3840, false); |
106 | | assert_eq!(result, (2160, 3720)); |
107 | | } |
108 | | |
109 | | #[test] |
110 | | fn resize_handles_zero() { |
111 | | let result = super::resize_dimensions(0, 100, 100, 100, false); |
112 | | assert_eq!(result, (1, 100)); |
113 | | |
114 | | let result = super::resize_dimensions(100, 0, 100, 100, false); |
115 | | assert_eq!(result, (100, 1)); |
116 | | |
117 | | let result = super::resize_dimensions(100, 100, 0, 100, false); |
118 | | assert_eq!(result, (1, 1)); |
119 | | |
120 | | let result = super::resize_dimensions(100, 100, 100, 0, false); |
121 | | assert_eq!(result, (1, 1)); |
122 | | } |
123 | | } |