/rust/registry/src/index.crates.io-1949cf8c6b5b557f/pic-scale-safe-0.1.12/src/compute_weights.rs
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) Radzivon Bartoshyk, 10/2024. All rights reserved. |
3 | | * |
4 | | * Redistribution and use in source and binary forms, with or without modification, |
5 | | * are permitted provided that the following conditions are met: |
6 | | * |
7 | | * 1. Redistributions of source code must retain the above copyright notice, this |
8 | | * list of conditions and the following disclaimer. |
9 | | * |
10 | | * 2. Redistributions in binary form must reproduce the above copyright notice, |
11 | | * this list of conditions and the following disclaimer in the documentation |
12 | | * and/or other materials provided with the distribution. |
13 | | * |
14 | | * 3. Neither the name of the copyright holder nor the names of its |
15 | | * contributors may be used to endorse or promote products derived from |
16 | | * this software without specific prior written permission. |
17 | | * |
18 | | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
19 | | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
20 | | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
21 | | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
22 | | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
23 | | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
24 | | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
25 | | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
26 | | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
27 | | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
28 | | */ |
29 | | use crate::filter_weights::{FilterBounds, FilterWeights}; |
30 | | use crate::math::{ConstPI, ConstSqrt2, Jinc}; |
31 | | use crate::sampler::ResamplingFunction; |
32 | | use num_traits::{AsPrimitive, Float, Signed}; |
33 | | use std::fmt::Debug; |
34 | | use std::ops::{AddAssign, Div, MulAssign, Neg}; |
35 | | |
36 | 0 | pub(crate) fn generate_weights<T>( |
37 | 0 | function: ResamplingFunction, |
38 | 0 | in_size: usize, |
39 | 0 | out_size: usize, |
40 | 0 | ) -> FilterWeights<T> |
41 | 0 | where |
42 | 0 | T: Copy |
43 | 0 | + Neg |
44 | 0 | + Signed |
45 | 0 | + Float |
46 | 0 | + 'static |
47 | 0 | + ConstPI |
48 | 0 | + MulAssign<T> |
49 | 0 | + AddAssign<T> |
50 | 0 | + AsPrimitive<f64> |
51 | 0 | + AsPrimitive<i64> |
52 | 0 | + AsPrimitive<usize> |
53 | 0 | + Jinc<T> |
54 | 0 | + ConstSqrt2 |
55 | 0 | + Default |
56 | 0 | + AsPrimitive<i32> |
57 | 0 | + Div<T, Output = T> |
58 | 0 | + Debug, |
59 | 0 | f32: AsPrimitive<T>, |
60 | 0 | f64: AsPrimitive<T>, |
61 | 0 | i64: AsPrimitive<T>, |
62 | 0 | i32: AsPrimitive<T>, |
63 | 0 | usize: AsPrimitive<T>, |
64 | | { |
65 | 0 | let resampling_filter = function.get_resampling_filter(); |
66 | 0 | let scale = in_size.as_() / out_size.as_(); |
67 | 0 | let is_resizable_kernel = resampling_filter.is_resizable_kernel; |
68 | 0 | let filter_scale_cutoff = match is_resizable_kernel { |
69 | 0 | true => scale.max(1f32.as_()), |
70 | 0 | false => 1f32.as_(), |
71 | | }; |
72 | 0 | let filter_base_size = resampling_filter.min_kernel_size * 2.; |
73 | 0 | let resampling_function = resampling_filter.kernel; |
74 | 0 | let window_func = resampling_filter.window; |
75 | | |
76 | 0 | let mut bounds: Vec<FilterBounds> = vec![FilterBounds::new(0, 0); out_size]; |
77 | | |
78 | 0 | let is_area = resampling_filter.is_area_filter && scale < 1.as_(); |
79 | | |
80 | 0 | if !is_area { |
81 | 0 | let base_size: usize = (filter_base_size.as_() * filter_scale_cutoff).round().as_(); |
82 | 0 | let kernel_size = base_size; |
83 | 0 | let filter_radius = base_size.as_() / 2.as_(); |
84 | 0 | let filter_scale = 1f32.as_() / filter_scale_cutoff; |
85 | 0 | let mut weights: Vec<T> = vec![T::default(); kernel_size * out_size]; |
86 | 0 | let mut local_filters = vec![T::default(); kernel_size]; |
87 | 0 | let mut filter_position = 0usize; |
88 | 0 | let blur_scale = match window_func { |
89 | 0 | None => 1f32.as_(), |
90 | 0 | Some(window) => { |
91 | 0 | if window.blur.as_() > 0f32.as_() { |
92 | 0 | 1f32.as_() / window.blur.as_() |
93 | | } else { |
94 | 0 | 0f32.as_() |
95 | | } |
96 | | } |
97 | | }; |
98 | | |
99 | 0 | for (i, bound) in bounds.iter_mut().enumerate() { |
100 | 0 | let center_x = ((i.as_() + 0.5.as_()) * scale).min(in_size.as_()); |
101 | 0 | let mut weights_sum: T = 0f32.as_(); |
102 | | |
103 | 0 | let start: usize = (center_x - filter_radius).floor().max(0f32.as_()).as_(); |
104 | 0 | let end: usize = (center_x + filter_radius) |
105 | 0 | .ceil() |
106 | 0 | .min(in_size.as_()) |
107 | 0 | .min(start.as_() + kernel_size.as_()) |
108 | 0 | .as_(); |
109 | | |
110 | 0 | let center = center_x - 0.5.as_(); |
111 | | |
112 | 0 | for (local_filter_iteration, k) in (start..end).enumerate() { |
113 | 0 | let dx = k.as_() - center; |
114 | | let weight; |
115 | 0 | if let Some(resampling_window) = window_func { |
116 | 0 | let mut x = dx.abs(); |
117 | 0 | x = if resampling_window.blur.as_() > 0f32.as_() { |
118 | 0 | x * blur_scale |
119 | | } else { |
120 | 0 | x |
121 | | }; |
122 | 0 | x = if x <= resampling_window.taper.as_() { |
123 | 0 | 0f32.as_() |
124 | | } else { |
125 | 0 | (x - resampling_window.taper.as_()) |
126 | 0 | / (1f32.as_() - resampling_window.taper.as_()) |
127 | | }; |
128 | 0 | let window_producer = resampling_window.window; |
129 | 0 | let x_kernel_scaled = x * filter_scale; |
130 | 0 | let window = if x < resampling_window.window_size.as_() { |
131 | 0 | window_producer(x_kernel_scaled * resampling_window.window_size.as_()) |
132 | | } else { |
133 | 0 | 0f32.as_() |
134 | | }; |
135 | 0 | weight = window * resampling_function(x_kernel_scaled); |
136 | 0 | } else { |
137 | 0 | let dx = dx.abs(); |
138 | 0 | weight = resampling_function(dx * filter_scale); |
139 | 0 | } |
140 | 0 | weights_sum += weight; |
141 | 0 | local_filters[local_filter_iteration] = weight; |
142 | | } |
143 | | |
144 | 0 | let size = end - start; |
145 | | |
146 | 0 | *bound = FilterBounds::new(start, size); |
147 | | |
148 | 0 | if weights_sum != 0f32.as_() { |
149 | 0 | let recpeq = 1f32.as_() / weights_sum; |
150 | | |
151 | 0 | for (dst, src) in weights |
152 | 0 | .iter_mut() |
153 | 0 | .skip(filter_position) |
154 | 0 | .take(size) |
155 | 0 | .zip(local_filters.iter().take(size)) |
156 | 0 | { |
157 | 0 | *dst = *src * recpeq; |
158 | 0 | } |
159 | 0 | } else if size > 0 { |
160 | 0 | for dst in weights.iter_mut().skip(filter_position).take(size) { |
161 | 0 | *dst = 0f32.as_(); |
162 | 0 | } |
163 | 0 | let center_tap: usize = (center - start.as_()).round().max(0f32.as_()).as_(); |
164 | 0 | weights[filter_position + center_tap.min(size - 1)] = 1f32.as_(); |
165 | 0 | } |
166 | | |
167 | 0 | filter_position += kernel_size; |
168 | | } |
169 | | |
170 | 0 | FilterWeights::<T>::new( |
171 | 0 | weights, |
172 | 0 | kernel_size, |
173 | 0 | kernel_size, |
174 | 0 | out_size, |
175 | 0 | filter_radius.as_(), |
176 | 0 | bounds, |
177 | | ) |
178 | | } else { |
179 | | // Simulating INTER_AREA from OpenCV, for up scaling here, |
180 | | // this is necessary because weight computation is different |
181 | | // from any other func |
182 | 0 | let inv_scale: T = 1.as_() / scale; |
183 | 0 | let kernel_size = 2; |
184 | 0 | let filter_radius: T = 1.as_(); |
185 | 0 | let mut weights: Vec<T> = vec![T::default(); kernel_size * out_size]; |
186 | 0 | let mut local_filters = vec![T::default(); kernel_size]; |
187 | 0 | let mut filter_position = 0usize; |
188 | | |
189 | 0 | for (i, bound) in bounds.iter_mut().enumerate() { |
190 | 0 | let mut weights_sum: T = 0f32.as_(); |
191 | | |
192 | 0 | let sx: T = (i.as_() * scale).floor(); |
193 | 0 | let fx = (i as i64 + 1).as_() - (sx + 1.as_()) * inv_scale; |
194 | 0 | let dx = if fx <= 0.as_() { |
195 | 0 | 0.as_() |
196 | | } else { |
197 | 0 | fx - fx.floor() |
198 | | }; |
199 | 0 | let dx = dx.abs(); |
200 | 0 | let weight0 = 1.as_() - dx; |
201 | 0 | let weight1: T = dx; |
202 | 0 | local_filters[0] = weight0; |
203 | 0 | local_filters[1] = weight1; |
204 | | |
205 | 0 | let start: usize = sx.floor().max(0f32.as_()).as_(); |
206 | 0 | let end: usize = (sx + kernel_size.as_()) |
207 | 0 | .ceil() |
208 | 0 | .min(in_size.as_()) |
209 | 0 | .min(start.as_() + kernel_size.as_()) |
210 | 0 | .as_(); |
211 | | |
212 | 0 | let size = end - start; |
213 | | |
214 | 0 | weights_sum += weight0; |
215 | 0 | if size > 1 { |
216 | 0 | weights_sum += weight1; |
217 | 0 | } |
218 | 0 | *bound = FilterBounds::new(start, size); |
219 | | |
220 | 0 | if weights_sum != 0f32.as_() { |
221 | 0 | let recpeq = 1f32.as_() / weights_sum; |
222 | | |
223 | 0 | for (dst, src) in weights |
224 | 0 | .iter_mut() |
225 | 0 | .skip(filter_position) |
226 | 0 | .take(size) |
227 | 0 | .zip(local_filters.iter().take(size)) |
228 | 0 | { |
229 | 0 | *dst = *src * recpeq; |
230 | 0 | } |
231 | 0 | } else { |
232 | 0 | weights[filter_position] = 1.as_(); |
233 | 0 | } |
234 | | |
235 | 0 | filter_position += kernel_size; |
236 | | } |
237 | | |
238 | 0 | FilterWeights::new( |
239 | 0 | weights, |
240 | 0 | kernel_size, |
241 | 0 | kernel_size, |
242 | 0 | out_size, |
243 | 0 | filter_radius.as_(), |
244 | 0 | bounds, |
245 | | ) |
246 | | } |
247 | 0 | } |
248 | | |
249 | | #[cfg(test)] |
250 | | mod tests { |
251 | | use super::*; |
252 | | |
253 | | #[test] |
254 | | fn every_window_has_weights_summing_to_one() { |
255 | | let functions = [ |
256 | | ResamplingFunction::Bilinear, |
257 | | ResamplingFunction::Lanczos3, |
258 | | ResamplingFunction::Gaussian, |
259 | | ResamplingFunction::Bartlett, |
260 | | ResamplingFunction::BartlettHann, |
261 | | ResamplingFunction::Lanczos3Jinc, |
262 | | ]; |
263 | | for function in functions { |
264 | | for (in_size, out_size) in [(100usize, 37usize), (37, 100), (256, 256), (5, 1)] { |
265 | | let filter = generate_weights::<f64>(function, in_size, out_size); |
266 | | for (i, chunk) in filter |
267 | | .weights |
268 | | .chunks_exact(filter.kernel_size) |
269 | | .enumerate() |
270 | | .take(out_size) |
271 | | { |
272 | | let sum: f64 = chunk.iter().sum(); |
273 | | assert!( |
274 | | (sum - 1.0).abs() < 1e-4, |
275 | | "{function:?} {in_size}->{out_size}: window {i} sums to {sum}" |
276 | | ); |
277 | | } |
278 | | } |
279 | | } |
280 | | } |
281 | | } |