/src/image/src/math/utils.rs
Line | Count | Source |
1 | | //! Shared mathematical utility functions. |
2 | | use std::cmp::max; |
3 | | use std::ops::{Add, Mul}; |
4 | | |
5 | | use num_traits::MulAdd; |
6 | | |
7 | | #[cfg(any( |
8 | | all( |
9 | | any(target_arch = "x86", target_arch = "x86_64"), |
10 | | target_feature = "fma" |
11 | | ), |
12 | | all(target_arch = "aarch64", target_feature = "neon") |
13 | | ))] |
14 | | #[inline(always)] |
15 | | /// Uses fused multiply add when available |
16 | | /// |
17 | | /// It is important not to call it if FMA flag is not turned on, |
18 | | /// Rust inserts libc `fmaf` based implementation here if FMA is clearly not available at compile time. |
19 | | /// This needs for speed only, one rounding error don't do anything useful here, thus it's blocked when |
20 | | /// we can't detect FMA availability at compile time. |
21 | | pub(crate) fn multiply_accumulate< |
22 | | T: Copy + Mul<T, Output = T> + Add<T, Output = T> + MulAdd<T, Output = T>, |
23 | | >( |
24 | | acc: T, |
25 | | a: T, |
26 | | b: T, |
27 | | ) -> T { |
28 | | MulAdd::mul_add(a, b, acc) |
29 | | } |
30 | | |
31 | | #[inline(always)] |
32 | | #[cfg(not(any( |
33 | | all( |
34 | | any(target_arch = "x86", target_arch = "x86_64"), |
35 | | target_feature = "fma" |
36 | | ), |
37 | | all(target_arch = "aarch64", target_feature = "neon") |
38 | | )))] |
39 | 0 | pub(crate) fn multiply_accumulate< |
40 | 0 | T: Copy + Mul<T, Output = T> + Add<T, Output = T> + MulAdd<T, Output = T>, |
41 | 0 | >( |
42 | 0 | acc: T, |
43 | 0 | a: T, |
44 | 0 | b: T, |
45 | 0 | ) -> T { |
46 | 0 | acc + a * b |
47 | 0 | } Unexecuted instantiation: image::math::utils::multiply_accumulate::<f32> Unexecuted instantiation: image::math::utils::multiply_accumulate::<u32> |
48 | | |
49 | | /// Calculates the width and height an image should be resized to. |
50 | | /// This preserves aspect ratio, and based on the `fill` parameter |
51 | | /// will either fill the dimensions to fit inside the smaller constraint |
52 | | /// (will overflow the specified bounds on one axis to preserve |
53 | | /// aspect ratio), or will shrink so that both dimensions are |
54 | | /// completely contained within the given `width` and `height`, |
55 | | /// with empty space on one axis. |
56 | 0 | pub(crate) fn resize_dimensions( |
57 | 0 | width: u32, |
58 | 0 | height: u32, |
59 | 0 | nwidth: u32, |
60 | 0 | nheight: u32, |
61 | 0 | fill: bool, |
62 | 0 | ) -> (u32, u32) { |
63 | 0 | let wratio = f64::from(nwidth) / f64::from(width); |
64 | 0 | let hratio = f64::from(nheight) / f64::from(height); |
65 | | |
66 | 0 | let ratio = if fill { |
67 | 0 | f64::max(wratio, hratio) |
68 | | } else { |
69 | 0 | f64::min(wratio, hratio) |
70 | | }; |
71 | | |
72 | 0 | let nw = max((f64::from(width) * ratio).round() as u64, 1); |
73 | 0 | let nh = max((f64::from(height) * ratio).round() as u64, 1); |
74 | | |
75 | 0 | if nw > u64::from(u32::MAX) { |
76 | 0 | let ratio = f64::from(u32::MAX) / f64::from(width); |
77 | 0 | (u32::MAX, max((f64::from(height) * ratio).round() as u32, 1)) |
78 | 0 | } else if nh > u64::from(u32::MAX) { |
79 | 0 | let ratio = f64::from(u32::MAX) / f64::from(height); |
80 | 0 | (max((f64::from(width) * ratio).round() as u32, 1), u32::MAX) |
81 | | } else { |
82 | 0 | (nw as u32, nh as u32) |
83 | | } |
84 | 0 | } |
85 | | |
86 | | #[cfg(test)] |
87 | | mod test { |
88 | | quickcheck! { |
89 | | fn resize_bounds_correctly_width(old_w: u32, new_w: u32) -> bool { |
90 | | if old_w == 0 || new_w == 0 { return true; } |
91 | | // In this case, the scaling is limited by scaling of height. |
92 | | // We could check that case separately but it does not conform to the same expectation. |
93 | | if u64::from(new_w) * 400u64 >= u64::from(old_w) * u64::from(u32::MAX) { return true; } |
94 | | |
95 | | let result = super::resize_dimensions(old_w, 400, new_w, u32::MAX, false); |
96 | | let exact = (400_f64 * f64::from(new_w) / f64::from(old_w)).round() as u32; |
97 | | result.0 == new_w && result.1 == exact.max(1) |
98 | | } |
99 | | } |
100 | | |
101 | | quickcheck! { |
102 | | fn resize_bounds_correctly_height(old_h: u32, new_h: u32) -> bool { |
103 | | if old_h == 0 || new_h == 0 { return true; } |
104 | | // In this case, the scaling is limited by scaling of width. |
105 | | // We could check that case separately but it does not conform to the same expectation. |
106 | | if 400u64 * u64::from(new_h) >= u64::from(old_h) * u64::from(u32::MAX) { return true; } |
107 | | |
108 | | let result = super::resize_dimensions(400, old_h, u32::MAX, new_h, false); |
109 | | let exact = (400_f64 * f64::from(new_h) / f64::from(old_h)).round() as u32; |
110 | | result.1 == new_h && result.0 == exact.max(1) |
111 | | } |
112 | | } |
113 | | |
114 | | #[test] |
115 | | fn resize_handles_fill() { |
116 | | let result = super::resize_dimensions(100, 200, 200, 500, true); |
117 | | assert!(result.0 == 250); |
118 | | assert!(result.1 == 500); |
119 | | |
120 | | let result = super::resize_dimensions(200, 100, 500, 200, true); |
121 | | assert!(result.0 == 500); |
122 | | assert!(result.1 == 250); |
123 | | } |
124 | | |
125 | | #[test] |
126 | | fn resize_never_rounds_to_zero() { |
127 | | let result = super::resize_dimensions(1, 150, 128, 128, false); |
128 | | assert!(result.0 > 0); |
129 | | assert!(result.1 > 0); |
130 | | } |
131 | | |
132 | | #[test] |
133 | | fn resize_handles_overflow() { |
134 | | let result = super::resize_dimensions(100, u32::MAX, 200, u32::MAX, true); |
135 | | assert!(result.0 == 100); |
136 | | assert!(result.1 == u32::MAX); |
137 | | |
138 | | let result = super::resize_dimensions(u32::MAX, 100, u32::MAX, 200, true); |
139 | | assert!(result.0 == u32::MAX); |
140 | | assert!(result.1 == 100); |
141 | | } |
142 | | |
143 | | #[test] |
144 | | fn resize_rounds() { |
145 | | // Only truncation will result in (3840, 2229) and (2160, 3719) |
146 | | let result = super::resize_dimensions(4264, 2476, 3840, 2160, true); |
147 | | assert_eq!(result, (3840, 2230)); |
148 | | |
149 | | let result = super::resize_dimensions(2476, 4264, 2160, 3840, false); |
150 | | assert_eq!(result, (2160, 3720)); |
151 | | } |
152 | | |
153 | | #[test] |
154 | | fn resize_handles_zero() { |
155 | | let result = super::resize_dimensions(0, 100, 100, 100, false); |
156 | | assert_eq!(result, (1, 100)); |
157 | | |
158 | | let result = super::resize_dimensions(100, 0, 100, 100, false); |
159 | | assert_eq!(result, (100, 1)); |
160 | | |
161 | | let result = super::resize_dimensions(100, 100, 0, 100, false); |
162 | | assert_eq!(result, (1, 1)); |
163 | | |
164 | | let result = super::resize_dimensions(100, 100, 100, 0, false); |
165 | | assert_eq!(result, (1, 1)); |
166 | | } |
167 | | } |