GCC Code Coverage Report


Directory: avs_core/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 53.5% 123 / 0 / 230
Functions: 37.0% 10 / 0 / 27
Branches: 51.8% 57 / 0 / 110

filters/resample_functions.cpp
Line Branch Exec Source
1 // Avisynth v2.5. Copyright 2002 Ben Rudiak-Gould et al.
2 // http://avisynth.nl
3
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation; either version 2 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA, or visit
17 // http://www.gnu.org/copyleft/gpl.html .
18 //
19 // Linking Avisynth statically or dynamically with other modules is making a
20 // combined work based on Avisynth. Thus, the terms and conditions of the GNU
21 // General Public License cover the whole combination.
22 //
23 // As a special exception, the copyright holders of Avisynth give you
24 // permission to link Avisynth with independent modules that communicate with
25 // Avisynth solely through the interfaces defined in avisynth.h, regardless of the license
26 // terms of these independent modules, and to copy and distribute the
27 // resulting combined work under terms of your choice, provided that
28 // every copy of the combined work is accompanied by a complete copy of
29 // the source code of Avisynth (the version of Avisynth used to produce the
30 // combined work), being distributed under the terms of the GNU General
31 // Public License plus this exception. An independent module is a module
32 // which is not derived from or based on Avisynth, such as 3rd-party filters,
33 // import and export plugins, or graphical user interfaces.
34
35 #include "resample_functions.h"
36 #include <cmath>
37 #include <vector>
38 #include <algorithm>
39 #include <avs/minmax.h>
40 #include <avs/alignment.h>
41
42
43 /*******************************************
44 ***************************************
45 ** Helper classes for resample.cpp **
46 ***************************************
47 *******************************************/
48
49 /***************************
50 ***** Point filter *****
51 **************************/
52
53 40 double PointFilter::f(double x) {
54 AVS_UNUSED(x);
55 40 return 1.0;
56 }
57
58
59 /***************************
60 ***** Triangle filter *****
61 **************************/
62
63 2211 double TriangleFilter::f(double x) {
64 2211 x = fabs(x);
65
2/2
✓ Branch 2 → 3 taken 1843 times.
✓ Branch 2 → 4 taken 368 times.
2211 return (x<1.0) ? 1.0-x : 0.0;
66 }
67
68
69
70
71
72 /*********************************
73 *** Mitchell-Netravali filter ***
74 *********************************/
75
76 5 MitchellNetravaliFilter::MitchellNetravaliFilter (double b, double c) {
77 5 p0 = ( 6. - 2.*b ) / 6.;
78 5 p2 = ( -18. + 12.*b + 6.*c ) / 6.;
79 5 p3 = ( 12. - 9.*b - 6.*c ) / 6.;
80 5 q0 = ( 8.*b + 24.*c ) / 6.;
81 5 q1 = ( - 12.*b - 48.*c ) / 6.;
82 5 q2 = ( 6.*b + 30.*c ) / 6.;
83 5 q3 = ( - b - 6.*c ) / 6.;
84 5 }
85
86 496 double MitchellNetravaliFilter::f (double x) {
87 496 x = fabs(x);
88
4/4
✓ Branch 2 → 3 taken 220 times.
✓ Branch 2 → 4 taken 276 times.
✓ Branch 4 → 5 taken 240 times.
✓ Branch 4 → 6 taken 36 times.
496 return (x<1) ? (p0+x*x*(p2+x*p3)) : (x<2) ? (q0+x*(q1+x*(q2+x*q3))) : 0.0;
89 }
90
91
92 /***********************
93 *** Lanczos3 filter ***
94 ***********************/
95 36 LanczosFilter::LanczosFilter(int _taps) {
96 36 taps = (double)clamp(_taps, 1, 100);
97 36 }
98
99 91864 double LanczosFilter::sinc(double value) {
100
2/2
✓ Branch 2 → 3 taken 91856 times.
✓ Branch 2 → 4 taken 8 times.
91864 if (value > 0.000001) {
101 91856 value *= M_PI;
102 91856 return sin(value) / value;
103 } else {
104 8 return 1.0;
105 }
106 }
107
108 45940 double LanczosFilter::f(double value) {
109 45940 value = fabs(value);
110
111
2/2
✓ Branch 2 → 3 taken 45932 times.
✓ Branch 2 → 6 taken 8 times.
45940 if (value < taps) {
112 45932 return (sinc(value) * sinc(value / taps));
113 } else {
114 8 return 0.0;
115 }
116 }
117
118
119 /***********************
120 *** Blackman filter ***
121 ***********************/
122 BlackmanFilter::BlackmanFilter(int _taps) {
123 taps = (double)clamp(_taps, 1, 100);
124 rtaps = 1.0/taps;
125 }
126
127 double BlackmanFilter::f(double value) {
128 value = fabs(value);
129
130 if (value < taps) {
131 if (value > 0.000001) {
132 value *= M_PI;
133 return (sin(value) / value) * (0.42 + 0.5*cos(value*rtaps) + 0.08*cos(2*value*rtaps));
134 } else {
135 return 1.0;
136 }
137 } else {
138 return 0.0;
139 }
140 }
141
142
143 /***********************
144 *** Spline16 filter ***
145 ***********************/
146
147 double Spline16Filter::f(double value) {
148 value = fabs(value);
149
150 if (value < 1.0) {
151 return ( ( value - 9.0/5.0 ) * value - 1.0/5.0 ) * value + 1.0;
152 } else if (value < 2.0) {
153 return ( ( -1.0/3.0 * (value-1.0) + 4.0/5.0 ) * (value-1.0) - 7.0/15.0 ) * (value-1.0);
154 }
155 return 0.0;
156 }
157
158 /***********************
159 *** Spline36 filter ***
160 ***********************/
161
162 double Spline36Filter::f(double value) {
163 value = fabs(value);
164
165 if (value < 1.0) {
166 return ( ( 13.0/11.0 * (value ) - 453.0/ 209.0 ) * (value ) - 3.0/ 209.0 ) *(value ) + 1.0;
167 } else if (value < 2.0) {
168 return ( ( -6.0/11.0 * (value-1.0) + 270.0/ 209.0 ) * (value-1.0) - 156.0/ 209.0 ) *(value-1.0);
169 } else if (value < 3.0) {
170 return ( ( 1.0/11.0 * (value-2.0) - 45.0/ 209.0 ) * (value-2.0) + 26.0/ 209.0 ) *(value-2.0);
171 }
172 return 0.0;
173 }
174
175 /***********************
176 *** Spline64 filter ***
177 ***********************/
178
179 double Spline64Filter::f(double value) {
180 value = fabs(value);
181
182 if (value < 1.0) {
183 return (( 49.0/41.0 * (value ) - 6387.0/2911.0) * (value ) - 3.0/2911.0) * (value ) + 1.0;
184 } else if (value < 2.0) {
185 return ((-24.0/41.0 * (value-1.0) + 4032.0/2911.0) * (value-1.0) - 2328.0/2911.0) * (value-1.0);
186 } else if (value < 3.0) {
187 return (( 6.0/41.0 * (value-2.0) - 1008.0/2911.0) * (value-2.0) + 582.0/2911.0) * (value-2.0);
188 } else if (value < 4.0) {
189 return ((- 1.0/41.0 * (value-3.0) + 168.0/2911.0) * (value-3.0) - 97.0/2911.0) * (value-3.0);
190 }
191 return 0.0;
192 }
193
194 /***********************
195 *** Gaussian filter ***
196 ***********************/
197
198 /* Solve taps from p*value*value < 9 as pow(2.0, -9.0) == 1.0/512.0 i.e 0.5 bit
199 value*value < 9/p p = param*0.1;
200 value*value < 90/param
201 value*value < 90/{0.1, 22.5, 30.0, 100.0}
202 value*value < {900, 4.0, 3.0, 0.9}
203 value < {30, 2.0, 1.73, 0.949} */
204
205 GaussianFilter::GaussianFilter(double p, double _b, double _s) {
206 param = clamp(p, 0.01, 100.0);
207 b = clamp(_b, 1.5, 3.5);
208 s = _s;
209 if (_s == 0) // auto-support signal
210 {
211 // get support from b and param for 0.01 of resudual kernel value
212 // equatiion is s = sqrt(-ln(0.01)/(param*ln(b))
213 // where ln(0.01) is about -4.6 and -ln(0.01) is 4.6
214 s = sqrt(4.6 / ((param * 0.1) * log(b)));
215 }
216 s = clamp(s, 0.1, 150.0);
217 }
218
219 double GaussianFilter::f(double value) {
220 double p = param * 0.1;
221 return pow(b, -p * value * value); // <3.7.4: b was fixed at 2.0
222 }
223
224 /***********************
225 *** Sinc filter ***
226 ***********************/
227 SincFilter::SincFilter(int _taps) {
228 taps = (double)clamp(_taps, 1, 150);
229 }
230
231 double SincFilter::f(double value) {
232 value = fabs(value);
233
234 if (value > 0.000001) {
235 value *= M_PI;
236 return sin(value)/value;
237 } else {
238 return 1.0;
239 }
240 }
241
242
243 /**********************
244 *** SinPower filter ***
245 ***********************/
246
247 SinPowerFilter::SinPowerFilter(double p) {
248 param = clamp(p, 1.0, 10.0);
249 }
250
251 double SinPowerFilter::f(double value) {
252 value = fabs(value);
253 value *= M_PI / param;
254
255 if (value < (M_PI / 2)) return pow(cos(value), 1.8);
256 else
257 {
258 if (value < M_PI) return -(cos(value) * cos(value)) / (0.9 * value);
259 else return 0;
260 }
261 }
262
263 /***********************
264 *** SincLin2 filter ***
265 ***********************/
266
267 SincLin2Filter::SincLin2Filter(int _taps)
268 {
269 taps = (double)clamp(_taps, 1, 30);
270 }
271
272 double SincLin2Filter::sinc(double value)
273 {
274 if (value > 0.000001)
275 {
276 value *= M_PI;
277 return sin(value) / value;
278 }
279 else return 1.0;
280 }
281
282 double SincLin2Filter::f(double value)
283 {
284 value = fabs(value);
285
286 if (value < (taps / 2.0)) return sinc(value);
287 else return sinc(value) * ((2.0 - (2.0 * value / taps)));
288
289 }
290
291
292 /*********************************
293 *** UserDefined2 filter ***
294 *********************************/
295
296 UserDefined2Filter::UserDefined2Filter(double _b, double _c, double _s)
297 {
298 a = 1.0; // 0 sample = 1
299 b = (double)clamp(_b, -50.0, 250.0); // 1 and -1 sample
300 c = (double)clamp(_c, -50.0, 250.0); // 2 and -2 sample
301 b = (b - 16.0) / 219.0;
302 c = (c - 16.0) / 219.0;
303 s = (double)clamp(_s, 1.5, 15.0); // filter support for resampler
304 }
305
306 double UserDefined2Filter::sinc(double value)
307 {
308
309 if (fabs(value) > 0.000001)
310 {
311 value *= M_PI;
312 return sin(value) / value;
313 }
314 else return 1.0;
315 }
316
317 double UserDefined2Filter::f(double x)
318 {
319 x = fabs(x);
320
321 return c * sinc(x + 2) + b * sinc(x + 1) + a * sinc(x) + b * sinc(x - 1) + c * sinc(x - 2);
322 }
323
324 /*
325 * OPTIMAL SCANLINE CALCULATION NOTES (L2 CACHE BLOCKING)
326 *
327 * This function calculates the optimal vertical strip size (max_scanline)
328 * to be processed in a cache-blocked horizontal resizing operation.
329 *
330 * CONTEXT: Single-threaded, high-throughput workload with private L2 cache.
331 * The high FPS target justifies a more aggressive cache reservation factor.
332 *
333 * 1. COEFFICIENT TABLE EXCLUSION (Horizontal 2x resize of fullhd content):
334 * The coefficient table (~245 KB) is excluded from the calculation. It is
335 * treated as a streamed resource due to its size relative to L2 (512 KB).
336 * The hardware prefetcher is expected to handle its sequential access pattern
337 * efficiently without residing fully in the reserved L2 working set.
338 *
339 * 2. CACHE RESERVATION HEURISTIC:
340 * A factor of 0.75 (3/4) is reserved for the working set. This is an aggressive
341 * approach for a single-threaded task with a private L2 cache, minimizing
342 * cache thrashing risk from OS context switches while maximizing block size.
343 *
344 * 3. CONCRETE SCENARIO (512 KB L2, 1920->3840 upscale):
345 * Reserved L2 space: 512 KB * 0.75 = 384 KB (393,216 bytes).
346 * One scanline strip (src + tgt) is 23,040 bytes.
347 * The resulting max_scanline is 17 (393,216 / 23,040 ~ 17.06).
348 */
349 120 int ResamplingProgram::resampler_h_detect_optimal_scanline(int src_width, int tgt_width, size_t l2_cache_size_bytes, size_t pixel_size) {
350 120 constexpr double CACHE_RESERVE_FACTOR = 0.75;
351
352 // Calculate the bytes needed for one (Source + Destination) scanline strip
353 120 size_t scanline_bytes = (static_cast<size_t>(src_width) + static_cast<size_t>(tgt_width)) * pixel_size;
354
355 // Calculate the reserved bytes based on the aggressive factor
356 // Use floating point math for precision, then cast to size_t
357 120 size_t reserved_l2_bytes = static_cast<size_t>(
358 120 static_cast<double>(l2_cache_size_bytes) * CACHE_RESERVE_FACTOR
359 );
360
361 // Calculate max_scanline (integer division for floor)
362 120 int max_scanline = static_cast<int>(reserved_l2_bytes / scanline_bytes);
363
364 // Clamp to practical bounds (4 to 64 is typical range for strip size)
365 // Dynamic by sample_size. For float32 (size=4) was 4 min and 64 max, so for uint8_t size=1 it will be 4x times more.
366 // Heuristic limits to avoid too small or too large strip sizes.
367 int iMinLimit;
368 int iMaxLimit;
369
370
3/4
✓ Branch 2 → 3 taken 12 times.
✓ Branch 2 → 4 taken 38 times.
✓ Branch 2 → 5 taken 70 times.
✗ Branch 2 → 6 not taken.
120 switch (pixel_size)
371 {
372 12 case 4: // float
373 12 iMinLimit = 4;
374 12 iMaxLimit = 64;
375 12 break;
376 38 case 2: // uint16_t
377 38 iMinLimit = 8;
378 38 iMaxLimit = 128;
379 38 break;
380 70 case 1: // uint8_t
381 70 iMinLimit = 16;
382 70 iMaxLimit = 256;
383 70 break;
384 default: // should never happen
385 iMinLimit = 4;
386 iMaxLimit = 64;
387 break;
388 }
389
390 120 max_scanline = std::min(std::max(max_scanline, iMinLimit), iMaxLimit);
391
392 120 return max_scanline;
393 }
394
395 /**
396 * @brief Checks if the data access pattern for horizontal resampling requires the slower Transpose method,
397 * or if the faster Permutex approach can be used.
398 *
399 * This function determines the feasibility of using vector instructions (like AVX2/AVX-512 Permutex)
400 * to load the source samples required for a group of output samples. This method is preferred when
401 * the total spread of necessary source samples is small.
402 *
403 * @note This check is performed for methods that process multiple output sample groups per loop pass
404 * (e.g., loading 2 groups of 8 samples for AVX-512).
405 *
406 * @param iSamplesInTheGroup The number of output samples processed in one vector iteration (e.g., 8, 16, 64).
407 * (Usually referred to as PIXELS_AT_A_TIME).
408 * @param permutex_index_diff_limit The maximum byte/element difference allowed between the earliest and latest
409 * required source sample for Permutex to be viable. This limit is dictated
410 * by the specific Permutex intrinsic used (e.g., 8 for _mm256_permutevar8x32_ps).
411 * @param kernel_size The size of the resampling filter kernel (number of coefficients).
412 * @return true If the source sample spread is too large, meaning only the Transpose method is allowed.
413 * @return false If the source sample spread is small enough, meaning the Permutex method can be used.
414 */
415 1 bool ResamplingProgram::resize_h_planar_gather_permutex_vstripe_check(int iSamplesInTheGroup, int permutex_index_diff_limit, int kernel_size)
416 {
417 // iSamplesInTheGroup is usually denoted as PIXELS_AT_A_TIME in H resampler code
418 // permutex_index_diff_limit is like iAccessibleSourceSamplesToGroup
419
420 // Alignment checks ensure safe access to pre-calculated arrays for the entire vector block.
421
422 // 'target_size_alignment' ensures safe access for the entire group (x to x + iSamplesInTheGroup - 1)
423 // Example: for iSamplesInTheGroup = 8, we need to ensure that
424 // - program->pixel_offset[x + 0] to program->pixel_offset[x + 7] and
425 // - corresponding coefficients (spread over coeff-strides like current_coeff + filter_size*0 to filter_size*7), where filter_size is the aligned coefficient stride.
426 // are valid if iSamplesInTheGroup = 8.
427
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 1 time.
1 assert(target_size_alignment >= iSamplesInTheGroup);
428
429 // Ensure that coefficient loading is safe for "kernel_size" element loads.
430 // But this is not true. For float pixel types, we can keep the alignment to 8, which can be less than kernel_size.
431 // It's because it depends on the filter's implementation. E.g. resize_h_planar_float_avx512_permutex_vstripe_ks16 has kernel_size=16.
432 // However, it uses gather loads from coeffs, which do not need to be aligned to kernel_size.
433 // uint8_t avx512 versions with ks16 do need 32 byte alignment, the 'short' coefficients stride is aligned to 32 bytes, that is 16 coeffs.
434 // So in this case, we need the alignment.
435 // The check cannot be generalized here, it is put in the specific resampler implementations if needed.
436 // assert(filter_size_alignment >= kernel_size);
437
438
1/2
✓ Branch 10 → 5 taken 1 time.
✗ Branch 10 → 11 not taken.
1 for (int x = 0; x < target_size; x += iSamplesInTheGroup) // check each group
439 {
440 // Get the index of the first required source sample for this group.
441 1 int start_off = pixel_offset[x + 0];
442
443 // Get the index of the last required source sample. This is the offset for the last
444 // output pixel in the group (x + iSamplesInTheGroup - 1) plus the last kernel tap (kernel_size - 1).
445 // Note: pixel_offset[] values for x >= target_size are pre-padded to match target_size-1 (ensured by resize_prepare_coeffs()).
446 1 const int end_off = pixel_offset[x + (iSamplesInTheGroup - 1)] + (kernel_size - 1);
447
448 // Check the total spread (difference) in source sample indices.
449 // This difference must be less than the limit imposed by the Permutex intrinsic's addressing capability.
450 // Examples of permutex_index_diff_limit:
451 // - 8 for _mm256_permutevar8x32_ps (float, avx2)
452 // - 32 for _mm512_permutex2var_ps (float, avx512)
453 // - 64 for _mm512_permutex2var_epi16 (uint16_t, avx512)
454 // - 128 for _mm512_permutex2var_epi8 (uint8_t, avx512)
455
1/2
✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 9 not taken.
1 if ((end_off - start_off) >= permutex_index_diff_limit) {
456 1 return true; // spread is too wide; only the transpose method is allowed.
457 }
458 }
459 return false; // Spread is acceptable; Permutex is OK.
460 }
461
462
463 /******************************
464 **** Resampling Patterns ****
465 *****************************/
466
467 120 ResamplingProgram* ResamplingFunction::GetResamplingProgram(int source_size, double crop_start, double crop_size, int target_size, int bits_per_pixel,
468 double center_pos_src, double center_pos_dst,
469 IScriptEnvironment* env)
470 {
471 // edge condition ideas from fmtconv, thanks.
472 120 double src_step = crop_size / double(target_size); // Distance between source pixels for adjacent dest pixels
473 120 double zc_size = std::max(src_step, 1.0) / 1.0; // Size of filter unit step (kernel_scale=1.0 in our case)
474 120 double imp_step = 1.0 / zc_size; // Corresponding distance in the impulse
475
1/2
✓ Branch 3 → 4 taken 120 times.
✗ Branch 3 → 71 not taken.
120 double filter_support = support() * zc_size; // Number of source pixels covered by the FIR
476
477 120 int fir_filter_size = std::max(int(std::ceil(filter_support * 2)), 1);
478 120 int max_kernel_size = 0;
479
480 120 const int last_line = source_size - 1;
481
482
3/8
✓ Branch 5 → 6 taken 120 times.
✗ Branch 5 → 71 not taken.
✓ Branch 6 → 7 taken 120 times.
✗ Branch 6 → 65 not taken.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 120 times.
✗ Branch 65 → 66 not taken.
✗ Branch 65 → 67 not taken.
120 ResamplingProgram* program = new ResamplingProgram(fir_filter_size, source_size, target_size, crop_start, crop_size, bits_per_pixel, env);
483
484 // Initial position calculation
485
486 120 double pos = crop_start;
487
488 /*
489 pre 3.7.4 logic:
490
491 Now in 2025, let's fact-check this comment.
492
493 pos = crop_start + ((crop_size - target_size) / (target_size*2)); // TODO this look wrong, gotta check
494 ==>
495 pos = crop_start + 1/2 * (crop_size / target_size - 1)
496 ==>
497 pos = crop_start + src_step * 0.5 - 1 * 0.5
498
499 fmtconv generic formula:
500
501 pos = crop_start + src_step * center_pos_dst - 1 * center_pos_src; // 3.7.4- fmtconv
502
503 Solved: center_pos_dst = 0.5, center_pos_src = 0.5 in old Avisynth
504
505 */
506
507 // Introduces an offset because samples are located at the center of the
508 // pixels, not on their boundaries. Excepted for pointresize.
509
2/2
✓ Branch 9 → 10 taken 108 times.
✓ Branch 9 → 11 taken 12 times.
120 if (filter_support > 0)
510 {
511 // Pre 3.7.4 Avisynth worked with fixed center_pos_dst = center_pos_src = 0.5
512 // Now it's externally configurable. In our use case they are always the same.
513 108 pos += src_step * center_pos_dst - 1 * center_pos_src;
514 }
515 else
516 {
517 // In case of PointResize(), which now returns real 0 for support().
518 // Avisynth heritage.
519 12 filter_support = 0.0001;
520 }
521
522
4/4
✓ Branch 12 → 13 taken 50 times.
✓ Branch 12 → 15 taken 70 times.
✓ Branch 13 → 14 taken 38 times.
✓ Branch 13 → 15 taken 12 times.
120 const int current_FPScale = (bits_per_pixel > 8 && bits_per_pixel <= 16) ? FPScale16 : FPScale;
523
524 120 std::vector<double> coef_tmp;
525
2/2
✓ Branch 61 → 18 taken 4421 times.
✓ Branch 61 → 62 taken 120 times.
4541 for (int i = 0; i < target_size; ++i) {
526 4421 coef_tmp.clear();
527
528 4421 int start_pos = (int)(pos + filter_support) - fir_filter_size + 1;
529 4421 program->pixel_offset[i] = clamp(start_pos, 0, last_line);
530
531 // First pass: Accumulate all coefficients for weighting
532 4421 double total = 0.0;
533
2/2
✓ Branch 25 → 22 taken 48687 times.
✓ Branch 25 → 26 taken 4421 times.
53108 for (int k = 0; k < fir_filter_size; ++k) {
534 48687 const int p = start_pos + k;
535
1/2
✓ Branch 22 → 23 taken 48687 times.
✗ Branch 22 → 68 not taken.
48687 double val = f((pos - p) * imp_step);
536
1/2
✓ Branch 23 → 24 taken 48687 times.
✗ Branch 23 → 68 not taken.
48687 coef_tmp.push_back(val);
537 48687 total += val;
538 }
539
540
1/2
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 28 taken 4421 times.
4421 if (total == 0.0) {
541 // Shouldn't happen for valid positions.
542 total = 1.0;
543 }
544
545 4421 const int coeff_arr_base_index = i * fir_filter_size;
546
547 // Second pass: Generate real coefficients, handling edge conditions
548 4421 double accu = 0.0;
549 4421 double prev_value = 0.0;
550
551 4421 int kernel_size = 0;
552
553
2/2
✓ Branch 28 → 29 taken 329 times.
✓ Branch 28 → 36 taken 4092 times.
4421 if (bits_per_pixel == 32) {
554 // Float version
555
2/2
✓ Branch 35 → 30 taken 841 times.
✓ Branch 35 → 43 taken 329 times.
1170 for (int k = 0; k < fir_filter_size; ++k) {
556 841 const int p = start_pos + k;
557 841 double val = coef_tmp[k];
558 841 accu += val;
559
4/4
✓ Branch 31 → 32 taken 830 times.
✓ Branch 31 → 34 taken 11 times.
✓ Branch 32 → 33 taken 818 times.
✓ Branch 32 → 34 taken 12 times.
841 if (p >= 0 && p <= last_line) {
560 818 program->pixel_coefficient_float[coeff_arr_base_index + kernel_size] = float(accu / total);
561 818 ++kernel_size;
562 818 accu = 0;
563 }
564 }
565 }
566 else {
567 // Integer version - using upscaled integer arithmetic (FPScale/FPScale16)
568
2/2
✓ Branch 42 → 37 taken 47846 times.
✓ Branch 42 → 43 taken 4092 times.
51938 for (int k = 0; k < fir_filter_size; ++k) {
569 47846 const int p = start_pos + k;
570 47846 double val = coef_tmp[k];
571 47846 accu += val;
572
4/4
✓ Branch 38 → 39 taken 46448 times.
✓ Branch 38 → 41 taken 1398 times.
✓ Branch 39 → 40 taken 45070 times.
✓ Branch 39 → 41 taken 1378 times.
47846 if (p >= 0 && p <= last_line) {
573 45070 double new_value = prev_value + accu / total;
574 // differential approach ensures the filter coefficients sum to exactly FPScale)
575 // The subtraction method guarantees that no matter how many terms we add, the
576 // final sum will be exactly equal to the fixed-point representation of 1.0.
577 45070 program->pixel_coefficient[coeff_arr_base_index + kernel_size] = (short)((int)(new_value * current_FPScale + 0.5) - int(prev_value * current_FPScale + 0.5));
578 45070 prev_value = new_value;
579 45070 ++kernel_size;
580 45070 accu = 0;
581 }
582 }
583 }
584
585 // We even haven't reached any valid line,
586 // or gathered accu values from past last line.
587
2/2
✓ Branch 43 → 44 taken 345 times.
✓ Branch 43 → 52 taken 4076 times.
4421 if (accu != 0)
588 {
589
2/2
✓ Branch 44 → 45 taken 335 times.
✓ Branch 44 → 48 taken 10 times.
345 if (kernel_size > 0) {
590 // Assign the remaining accumulator to the last line, just like we put
591 // the accumulator before the first valid line to the first line.
592
2/2
✓ Branch 45 → 46 taken 9 times.
✓ Branch 45 → 47 taken 326 times.
335 if (bits_per_pixel == 32)
593 9 program->pixel_coefficient_float[coeff_arr_base_index + kernel_size - 1] += float(accu / total);
594 else {
595 326 double new_value = prev_value + accu / total;
596 326 program->pixel_coefficient[coeff_arr_base_index + kernel_size - 1] += (short)((int)(new_value * current_FPScale + 0.5) - int(prev_value * current_FPScale + 0.5));
597 }
598 // no change in kernel_size
599 }
600 else
601 {
602 // new entry, accu/total must be 1.0 here (we always normalize)
603
1/2
✗ Branch 48 → 49 not taken.
✓ Branch 48 → 50 taken 10 times.
10 if (bits_per_pixel == 32)
604 program->pixel_coefficient_float[coeff_arr_base_index + kernel_size] = float(accu / total);
605 else
606 10 program->pixel_coefficient[coeff_arr_base_index + kernel_size] = (short)((int)(accu / total * current_FPScale + 0.5));
607 10 ++kernel_size;
608 }
609 }
610
611
1/2
✗ Branch 52 → 53 not taken.
✓ Branch 52 → 57 taken 4421 times.
4421 if (kernel_size == 0) {
612 // write a single 1.0 coeff entry
613 if (bits_per_pixel == 32)
614 program->pixel_coefficient_float[coeff_arr_base_index + kernel_size] = 1.0f;
615 else
616 program->pixel_coefficient[coeff_arr_base_index + kernel_size] = (short)((int)(1.0 * current_FPScale + 0.5));
617 ++kernel_size;
618 }
619
620 4421 program->kernel_sizes[i] = kernel_size;
621
2/2
✓ Branch 58 → 59 taken 311 times.
✓ Branch 58 → 60 taken 4110 times.
4421 if (kernel_size > max_kernel_size) max_kernel_size = kernel_size;
622
623 4421 pos += src_step;
624 }
625
626 // the different kernel sizes and coeff table will be later postprocessed
627 // to have aligned and equally sized coefficients.
628
629 120 program->filter_size_real = max_kernel_size;
630 // can be less than original filter size if source dimensions are small
631
632 120 return program;
633 120 }
634
635
636 #if 0
637 // old pre 3.7.4, kept for reference
638 ResamplingProgram* ResamplingFunction::GetResamplingProgram(int source_size, double crop_start, double crop_size, int target_size, int bits_per_pixel, IScriptEnvironment* env)
639 {
640 double filter_scale = double(target_size) / crop_size;
641 double filter_step = min(filter_scale, 1.0);
642 double filter_support = support() / filter_step;
643 int fir_filter_size = int(ceil(filter_support*2));
644
645 ResamplingProgram* program = new ResamplingProgram(fir_filter_size, source_size, target_size, crop_start, crop_size, bits_per_pixel, env);
646
647 // this variable translates such that the image center remains fixed
648 double pos;
649 double pos_step = crop_size / target_size;
650
651 if (source_size <= filter_support) {
652 env->ThrowError("Resize: Source image too small for this resize method. Width=%d, Support=%d", source_size, int(ceil(filter_support)));
653 }
654
655 if (fir_filter_size == 1) // PointResize
656 pos = crop_start;
657 else
658 pos = crop_start + ((crop_size - target_size) / (target_size*2)); // TODO this look wrong, gotta check
659
660 const int current_FPScale = (bits_per_pixel > 8 && bits_per_pixel <= 16) ? FPScale16 : FPScale;
661
662 for (int i = 0; i < target_size; ++i) {
663 // Clamp start and end position such that it does not exceed frame size
664 int end_pos = int(pos + filter_support);
665
666 if (end_pos > source_size-1)
667 end_pos = source_size-1;
668
669 int start_pos = end_pos - fir_filter_size + 1;
670
671 if (start_pos < 0)
672 start_pos = 0;
673
674 program->pixel_offset[i] = start_pos;
675
676 // check the simd-optimized (8 pixels and filter coefficients at a time) limit to not reach beyond the last pixel
677 // in order not to have NaN floats
678 if (start_pos + AlignNumber(fir_filter_size, ALIGN_FLOAT_RESIZER_COEFF_SIZE) - 1 > source_size - 1)
679 {
680 if (!program->overread_possible_filter_size_aligned) {
681 // register the first occurance
682 program->overread_possible_filter_size_aligned = true;
683 program->source_overread_offset = start_pos;
684 program->source_overread_beyond_targetx = i;
685 }
686 }
687
688 // the following code ensures that the coefficients add to exactly FPScale or FPScale16
689 double total = 0.0;
690
691 // Ensure that we have a valid position
692 double ok_pos = clamp(pos, 0.0, (double)(source_size-1));
693
694 // Accumulate all coefficients for weighting
695 for (int j = 0; j < fir_filter_size; ++j) {
696 total += f((start_pos+j - ok_pos) * filter_step);
697 }
698
699 if (total == 0.0) {
700 // Shouldn't happened for valid positions.
701 total = 1.0;
702 }
703
704 double value = 0.0;
705
706 // Now we generate real coefficient
707 if (bits_per_pixel == 32) {
708 // float
709 for (int k = 0; k < fir_filter_size; ++k) {
710 double new_value = value + f((start_pos + k - ok_pos) * filter_step) / total;
711 program->pixel_coefficient_float[i*fir_filter_size + k] = float(new_value - value); // no scaling for float
712 value = new_value;
713 }
714 }
715 else {
716 for (int k = 0; k < fir_filter_size; ++k) {
717 double new_value = value + f((start_pos + k - ok_pos) * filter_step) / total;
718 // FIXME: is it correct to round negative values upwards?
719 // Answer : No with int cast, yes with floor() instead.
720 program->pixel_coefficient[i*fir_filter_size + k] = (short)((int)(new_value*current_FPScale + 0.5) - int(value*current_FPScale + 0.5)); // to make it round across pixels
721 value = new_value;
722 }
723 }
724
725 pos += pos_step;
726 }
727
728 // aligned as 8, now fill with safe values for 8 pixels/cycle simd loop
729 for (int i = target_size; i < AlignNumber(target_size, ALIGN_RESIZER_TARGET_SIZE); ++i)
730 program->pixel_offset[i] = source_size - fir_filter_size;
731
732 return program;
733 }
734 #endif
735