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 | pub(crate) fn resize_dimensions( |
13 | width: u32, |
14 | height: u32, |
15 | nwidth: u32, |
16 | nheight: u32, |
17 | fill: bool, |
18 | ) -> (u32, u32) { |
19 | let wratio: f64 = nwidth as f64 / width as f64; |
20 | let hratio: f64 = nheight as f64 / height as f64; |
21 | |
22 | let ratio: f64 = if fill { |
23 | f64::max(self:wratio, other:hratio) |
24 | } else { |
25 | f64::min(self:wratio, other:hratio) |
26 | }; |
27 | |
28 | let nw: u64 = max((width as f64 * ratio).round() as u64, v2:1); |
29 | let nh: u64 = max((height as f64 * ratio).round() as u64, v2:1); |
30 | |
31 | if nw > u64::from(u32::MAX) { |
32 | let ratio: f64 = u32::MAX as f64 / width as f64; |
33 | (u32::MAX, max((height as f64 * ratio).round() as u32, v2:1)) |
34 | } else if nh > u64::from(u32::MAX) { |
35 | let ratio: f64 = u32::MAX as f64 / height as f64; |
36 | (max((width as f64 * ratio).round() as u32, v2:1), u32::MAX) |
37 | } else { |
38 | (nw as u32, nh as u32) |
39 | } |
40 | } |
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 new_w as u64 * 400u64 >= old_w as u64 * u64::from(u32::MAX) { return true; } |
50 | |
51 | let result = super::resize_dimensions(old_w, 400, new_w, ::std::u32::MAX, false); |
52 | let exact = (400_f64 * new_w as f64 / old_w as f64).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 * new_h as u64 >= old_h as u64 * u64::from(u32::MAX) { return true; } |
63 | |
64 | let result = super::resize_dimensions(400, old_h, ::std::u32::MAX, new_h, false); |
65 | let exact = (400_f64 * new_h as f64 / old_h as f64).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, ::std::u32::MAX, 200, ::std::u32::MAX, true); |
91 | assert!(result.0 == 100); |
92 | assert!(result.1 == ::std::u32::MAX); |
93 | |
94 | let result = super::resize_dimensions(::std::u32::MAX, 100, ::std::u32::MAX, 200, true); |
95 | assert!(result.0 == ::std::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 | } |
124 | |