Coverage Report

Created: 2026-09-02 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}