Branch data Line data Source code
1 : : /*
2 : : * This file is part of libplacebo.
3 : : *
4 : : * libplacebo is free software; you can redistribute it and/or
5 : : * modify it under the terms of the GNU Lesser General Public
6 : : * License as published by the Free Software Foundation; either
7 : : * version 2.1 of the License, or (at your option) any later version.
8 : : *
9 : : * libplacebo 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 Lesser General Public License for more details.
13 : : *
14 : : * You should have received a copy of the GNU Lesser General Public
15 : : * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
16 : : */
17 : :
18 : : /*
19 : : * Some of the filter code originally derives (via mpv) from Glumpy:
20 : : * # Copyright (c) 2009-2016 Nicolas P. Rougier. All rights reserved.
21 : : * # Distributed under the (new) BSD License.
22 : : * (https://github.com/glumpy/glumpy/blob/master/glumpy/library/build-spatial-filters.py)
23 : : *
24 : : * The math underlying each filter function was written from scratch, with
25 : : * some algorithms coming from a number of different sources, including:
26 : : * - https://en.wikipedia.org/wiki/Window_function
27 : : * - https://en.wikipedia.org/wiki/Jinc
28 : : * - http://vector-agg.cvs.sourceforge.net/viewvc/vector-agg/agg-2.5/include/agg_image_filters.h
29 : : * - Vapoursynth plugin fmtconv (WTFPL Licensed), which is based on
30 : : * dither plugin for avisynth from the same author:
31 : : * https://github.com/vapoursynth/fmtconv/tree/master/src/fmtc
32 : : * - Paul Heckbert's "zoom"
33 : : * - XBMC: ConvolutionKernels.cpp etc.
34 : : * - https://github.com/AviSynth/jinc-resize (only used to verify the math)
35 : : */
36 : :
37 : : #include <math.h>
38 : :
39 : : #include "common.h"
40 : : #include "filters.h"
41 : : #include "log.h"
42 : :
43 : : #ifdef PL_HAVE_WIN32
44 : : #define j1 _j1
45 : : #endif
46 : :
47 : 1482 : bool pl_filter_function_eq(const struct pl_filter_function *a,
48 : : const struct pl_filter_function *b)
49 : : {
50 [ + + + + ]: 1482 : return (a ? a->weight : NULL) == (b ? b->weight : NULL);
51 : : }
52 : :
53 : 1225 : bool pl_filter_config_eq(const struct pl_filter_config *a,
54 : : const struct pl_filter_config *b)
55 : : {
56 [ - + ]: 1225 : if (!a || !b)
57 : 0 : return a == b;
58 : :
59 [ + + ]: 1482 : bool eq = pl_filter_function_eq(a->kernel, b->kernel) &&
60 : 257 : pl_filter_function_eq(a->window, b->window) &&
61 [ + + ]: 225 : a->radius == b->radius &&
62 [ - + ]: 217 : a->clamp == b->clamp &&
63 [ + + ]: 217 : a->blur == b->blur &&
64 [ - + ]: 209 : a->taper == b->taper &&
65 [ + + + + ]: 1434 : a->polar == b->polar &&
66 [ - + ]: 169 : a->antiring == b->antiring;
67 : :
68 [ + + ]: 3675 : for (int i = 0; i < PL_FILTER_MAX_PARAMS; i++) {
69 [ + + ]: 2450 : if (a->kernel->tunable[i])
70 : 728 : eq &= a->params[i] == b->params[i];
71 [ + + - + ]: 2450 : if (a->window && a->window->tunable[i])
72 : 0 : eq &= a->wparams[i] == b->wparams[i];
73 : : }
74 : :
75 : : return eq;
76 : : }
77 : :
78 [ + + ]: 474541 : double pl_filter_sample(const struct pl_filter_config *c, double x)
79 : : {
80 : : const float radius = pl_filter_radius_bound(c);
81 : :
82 : : // All filters are symmetric, and in particular only need to be defined
83 : : // for [0, radius].
84 : 474541 : x = fabs(x);
85 : :
86 : : // Return early for values outside of the kernel radius, since the functions
87 : : // are not necessarily valid outside of this interval. No such check is
88 : : // needed for the window, because it's always stretched to fit.
89 [ + + ]: 474541 : if (x > radius)
90 : : return 0.0;
91 : :
92 : : // Apply the blur and taper coefficients as needed
93 [ + + ]: 473996 : double kx = x <= c->taper ? 0.0 : (x - c->taper) / (1.0 - c->taper / radius);
94 [ + + ]: 473996 : if (c->blur > 0.0)
95 : 440173 : kx /= c->blur;
96 : :
97 [ - + ]: 473996 : pl_assert(!c->kernel->opaque);
98 : 473996 : double k = c->kernel->weight(&(const struct pl_filter_ctx) {
99 : : .radius = radius,
100 : : .params = {
101 [ + + ]: 473996 : c->kernel->tunable[0] ? c->params[0] : c->kernel->params[0],
102 [ + + ]: 473996 : c->kernel->tunable[1] ? c->params[1] : c->kernel->params[1],
103 : : },
104 : : }, kx);
105 : :
106 : : // Apply the optional windowing function
107 [ + + ]: 473996 : if (c->window) {
108 [ - + ]: 140513 : pl_assert(!c->window->opaque);
109 : 140513 : double wx = x / radius * c->window->radius;
110 : 140513 : k *= c->window->weight(&(struct pl_filter_ctx) {
111 : : .radius = c->window->radius,
112 : : .params = {
113 [ - + ]: 140513 : c->window->tunable[0] ? c->wparams[0] : c->window->params[0],
114 [ - + ]: 140513 : c->window->tunable[1] ? c->wparams[1] : c->window->params[1],
115 : : },
116 : : }, wx);
117 : : }
118 : :
119 [ + + ]: 473996 : return k < 0 ? (1 - c->clamp) * k : k;
120 : : }
121 : :
122 [ + + ]: 289 : static void filter_cutoffs(const struct pl_filter_config *c, float cutoff,
123 : : float *out_radius, float *out_radius_zero)
124 : : {
125 : : const float bound = pl_filter_radius_bound(c);
126 : 289 : float prev = 0.0, fprev = pl_filter_sample(c, prev);
127 : : bool found_root = false;
128 : :
129 : : const float step = 1e-2f;
130 [ + + ]: 103815 : for (float x = 0.0; x < bound + step; x += step) {
131 : 103526 : float fx = pl_filter_sample(c, x);
132 [ + + + + : 103526 : if ((fprev > cutoff && fx <= cutoff) || (fprev < -cutoff && fx >= -cutoff)) {
+ + + + ]
133 : : // Found zero crossing
134 : 683 : float root = x - fx * (x - prev) / (fx - fprev); // secant method
135 : 683 : root = fminf(root, bound);
136 : 683 : *out_radius = root;
137 [ + + ]: 683 : if (!found_root) // first root
138 : 289 : *out_radius_zero = root;
139 : : found_root = true;
140 : : }
141 : : prev = x;
142 : : fprev = fx;
143 : : }
144 : :
145 [ - + ]: 289 : if (!found_root)
146 : 0 : *out_radius_zero = *out_radius = bound;
147 : 289 : }
148 : :
149 : : // Compute a single row of weights for a given filter in one dimension, indexed
150 : : // by the indicated subpixel offset. Writes `f->row_size` values to `out`.
151 : 51968 : static void compute_row(struct pl_filter_t *f, double offset, float *out)
152 : : {
153 : : double wsum = 0.0;
154 [ + + ]: 390912 : for (int i = 0; i < f->row_size; i++) {
155 : : // For the example of a filter with row size 4 and offset 0.3, we have:
156 : : //
157 : : // 0 1 * 2 3
158 : : //
159 : : // * indicates the sampled position. What we want to compute is the
160 : : // distance from each index to that sampled position.
161 [ - + ]: 338944 : pl_assert(f->row_size % 2 == 0);
162 : 338944 : const int base = f->row_size / 2 - 1; // index to the left of the center
163 : 338944 : const double center = base + offset; // offset of center relative to idx 0
164 : 338944 : double w = pl_filter_sample(&f->params.config, i - center);
165 : 338944 : out[i] = w;
166 : 338944 : wsum += w;
167 : : }
168 : :
169 : : // Readjust weights to preserve energy
170 [ + - ]: 51968 : pl_assert(wsum > 0);
171 [ + + ]: 390912 : for (int i = 0; i < f->row_size; i++)
172 : 338944 : out[i] /= wsum;
173 : 51968 : }
174 : :
175 : : // Needed for backwards compatibility with v1 configuration API
176 : : static struct pl_filter_function *dupfilter(void *alloc,
177 : : const struct pl_filter_function *f)
178 : : {
179 : 382 : return f ? pl_memdup(alloc, (void *)f, sizeof(*f)) : NULL;
180 : : }
181 : :
182 : 289 : pl_filter pl_filter_generate(pl_log log, const struct pl_filter_params *params)
183 : : {
184 [ - + ]: 289 : pl_assert(params);
185 [ + - - + ]: 289 : if (params->lut_entries <= 0 || !params->config.kernel) {
186 : 0 : pl_fatal(log, "Invalid params: missing lut_entries or config.kernel");
187 : 0 : return NULL;
188 : : }
189 : :
190 [ - + ]: 289 : if (params->config.kernel->opaque) {
191 : 0 : pl_err(log, "Trying to use opaque kernel '%s' in non-opaque context!",
192 : : params->config.kernel->name);
193 : 0 : return NULL;
194 : : }
195 : :
196 [ + + - + ]: 289 : if (params->config.window && params->config.window->opaque) {
197 : 0 : pl_err(log, "Trying to use opaque window '%s' in non-opaque context!",
198 : : params->config.window->name);
199 : 0 : return NULL;
200 : : }
201 : :
202 : 289 : struct pl_filter_t *f = pl_zalloc_ptr(NULL, f);
203 : 289 : f->params = *params;
204 [ + - ]: 289 : f->params.config.kernel = dupfilter(f, params->config.kernel);
205 [ + + ]: 289 : f->params.config.window = dupfilter(f, params->config.window);
206 : :
207 : : // Compute main lobe and total filter size
208 : 289 : filter_cutoffs(¶ms->config, params->cutoff, &f->radius, &f->radius_zero);
209 : 289 : f->radius_cutoff = f->radius; // backwards compatibility
210 : :
211 : : float *weights;
212 [ + + ]: 289 : if (params->config.polar) {
213 : : // Compute a 1D array indexed by radius
214 : 86 : weights = pl_alloc(f, params->lut_entries * sizeof(float));
215 [ + + ]: 22102 : for (int i = 0; i < params->lut_entries; i++) {
216 : 22016 : double x = f->radius * i / (params->lut_entries - 1);
217 : 22016 : weights[i] = pl_filter_sample(¶ms->config, x);
218 : : }
219 : : } else {
220 : : // Pick the most appropriate row size
221 : 203 : f->row_size = ceilf(f->radius) * 2;
222 [ + + - + ]: 203 : if (params->max_row_size && f->row_size > params->max_row_size) {
223 : 0 : pl_info(log, "Required filter size %d exceeds the maximum allowed "
224 : : "size of %d. This may result in adverse effects (aliasing, "
225 : : "or moiré artifacts).", f->row_size, params->max_row_size);
226 : 0 : f->row_size = params->max_row_size;
227 : 0 : f->insufficient = true;
228 : : }
229 [ + + ]: 203 : f->row_stride = PL_ALIGN(f->row_size, params->row_stride_align);
230 : :
231 : : // Compute a 2D array indexed by the subpixel position
232 : 203 : weights = pl_calloc(f, params->lut_entries * f->row_stride, sizeof(float));
233 [ + + ]: 52171 : for (int i = 0; i < params->lut_entries; i++) {
234 : 51968 : compute_row(f, i / (double)(params->lut_entries - 1),
235 : 51968 : weights + f->row_stride * i);
236 : : }
237 : : }
238 : :
239 : 289 : f->weights = weights;
240 : 289 : return f;
241 : : }
242 : :
243 : 310 : void pl_filter_free(pl_filter *filter)
244 : : {
245 : 310 : pl_free_ptr((void **) filter);
246 : 310 : }
247 : :
248 : : // Built-in filter functions
249 : :
250 : 5222 : static double box(const struct pl_filter_ctx *f, double x)
251 : : {
252 : 5222 : return 1.0;
253 : : }
254 : :
255 : : const struct pl_filter_function pl_filter_function_box = {
256 : : .weight = box,
257 : : .name = "box",
258 : : .radius = 1.0,
259 : : .resizable = true,
260 : : };
261 : :
262 : : static const struct pl_filter_function filter_function_dirichlet = {
263 : : .name = "dirichlet", // alias
264 : : .weight = box,
265 : : .radius = 1.0,
266 : : .resizable = true,
267 : : };
268 : :
269 : 21459 : static double triangle(const struct pl_filter_ctx *f, double x)
270 : : {
271 : 21459 : return 1.0 - x / f->radius;
272 : : }
273 : :
274 : : const struct pl_filter_function pl_filter_function_triangle = {
275 : : .name = "triangle",
276 : : .weight = triangle,
277 : : .radius = 1.0,
278 : : .resizable = true,
279 : : };
280 : :
281 : 2 : static double cosine(const struct pl_filter_ctx *f, double x)
282 : : {
283 : 2 : return cos(x);
284 : : }
285 : :
286 : : const struct pl_filter_function pl_filter_function_cosine = {
287 : : .name = "cosine",
288 : : .weight = cosine,
289 : : .radius = M_PI / 2.0,
290 : : };
291 : :
292 : 15056 : static double hann(const struct pl_filter_ctx *f, double x)
293 : : {
294 : 15056 : return 0.5 + 0.5 * cos(M_PI * x);
295 : : }
296 : :
297 : : const struct pl_filter_function pl_filter_function_hann = {
298 : : .name = "hann",
299 : : .weight = hann,
300 : : .radius = 1.0,
301 : : };
302 : :
303 : : static const struct pl_filter_function filter_function_hanning = {
304 : : .name = "hanning", // alias
305 : : .weight = hann,
306 : : .radius = 1.0,
307 : : };
308 : :
309 : 1 : static double hamming(const struct pl_filter_ctx *f, double x)
310 : : {
311 : 1 : return 0.54 + 0.46 * cos(M_PI * x);
312 : : }
313 : :
314 : : const struct pl_filter_function pl_filter_function_hamming = {
315 : : .name = "hamming",
316 : : .weight = hamming,
317 : : .radius = 1.0,
318 : : };
319 : :
320 : 1 : static double welch(const struct pl_filter_ctx *f, double x)
321 : : {
322 : 1 : return 1.0 - x * x;
323 : : }
324 : :
325 : : const struct pl_filter_function pl_filter_function_welch = {
326 : : .name = "welch",
327 : : .weight = welch,
328 : : .radius = 1.0,
329 : : };
330 : :
331 : : static double bessel_i0(double x)
332 : : {
333 : : double s = 1.0;
334 : 2 : double y = x * x / 4.0;
335 : : double t = y;
336 : : int i = 2;
337 [ + + + + ]: 20 : while (t > 1e-12) {
338 : 18 : s += t;
339 : 18 : t *= y / (i * i);
340 : 18 : i += 1;
341 : : }
342 : : return s;
343 : : }
344 : :
345 : 1 : static double kaiser(const struct pl_filter_ctx *f, double x)
346 : : {
347 : 1 : double alpha = fmax(f->params[0], 0.0);
348 : : double scale = bessel_i0(alpha);
349 : 1 : return bessel_i0(alpha * sqrt(1.0 - x * x)) / scale;
350 : : }
351 : :
352 : : const struct pl_filter_function pl_filter_function_kaiser = {
353 : : .name = "kaiser",
354 : : .weight = kaiser,
355 : : .radius = 1.0,
356 : : .params = {2.0},
357 : : .tunable = {true},
358 : : };
359 : :
360 : 1 : static double blackman(const struct pl_filter_ctx *f, double x)
361 : : {
362 : 1 : double a = f->params[0];
363 : 1 : double a0 = (1 - a) / 2.0, a1 = 1 / 2.0, a2 = a / 2.0;
364 : 1 : x *= M_PI;
365 : 1 : return a0 + a1 * cos(x) + a2 * cos(2 * x);
366 : : }
367 : :
368 : : const struct pl_filter_function pl_filter_function_blackman = {
369 : : .name = "blackman",
370 : : .weight = blackman,
371 : : .radius = 1.0,
372 : : .params = {0.16},
373 : : .tunable = {true},
374 : : };
375 : :
376 : 1 : static double bohman(const struct pl_filter_ctx *f, double x)
377 : : {
378 : 1 : double pix = M_PI * x;
379 : 1 : return (1.0 - x) * cos(pix) + sin(pix) / M_PI;
380 : : }
381 : :
382 : : const struct pl_filter_function pl_filter_function_bohman = {
383 : : .name = "bohman",
384 : : .weight = bohman,
385 : : .radius = 1.0,
386 : : };
387 : :
388 : 20827 : static double gaussian(const struct pl_filter_ctx *f, double x)
389 : : {
390 : 20827 : return exp(-2.0 * x * x / f->params[0]);
391 : : }
392 : :
393 : : const struct pl_filter_function pl_filter_function_gaussian = {
394 : : .name = "gaussian",
395 : : .weight = gaussian,
396 : : .radius = 2.0,
397 : : .resizable = true,
398 : : .params = {1.0},
399 : : .tunable = {true},
400 : : };
401 : :
402 : 4 : static double quadratic(const struct pl_filter_ctx *f, double x)
403 : : {
404 [ + + ]: 4 : if (x < 0.5) {
405 : 2 : return 1.0 - 4.0/3.0 * (x * x);
406 : : } else {
407 : 2 : return 2.0 / 3.0 * (x - 1.5) * (x - 1.5);
408 : : }
409 : : }
410 : :
411 : : const struct pl_filter_function pl_filter_function_quadratic = {
412 : : .name = "quadratic",
413 : : .weight = quadratic,
414 : : .radius = 1.5,
415 : : };
416 : :
417 : : static const struct pl_filter_function filter_function_quadric = {
418 : : .name = "quadric", // alias
419 : : .weight = quadratic,
420 : : .radius = 1.5,
421 : : };
422 : :
423 : 175940 : static double sinc(const struct pl_filter_ctx *f, double x)
424 : : {
425 [ + + ]: 175940 : if (x < 1e-8)
426 : : return 1.0;
427 : 175638 : x *= M_PI;
428 : 175638 : return sin(x) / x;
429 : : }
430 : :
431 : : const struct pl_filter_function pl_filter_function_sinc = {
432 : : .name = "sinc",
433 : : .weight = sinc,
434 : : .radius = 1.0,
435 : : .resizable = true,
436 : : };
437 : :
438 : 128196 : static double jinc(const struct pl_filter_ctx *f, double x)
439 : : {
440 [ + + ]: 128196 : if (x < 1e-8)
441 : : return 1.0;
442 : 127807 : x *= M_PI;
443 : 127807 : return 2.0 * j1(x) / x;
444 : : }
445 : :
446 : : const struct pl_filter_function pl_filter_function_jinc = {
447 : : .name = "jinc",
448 : : .weight = jinc,
449 : : .radius = 1.2196698912665045, // first zero
450 : : .resizable = true,
451 : : };
452 : :
453 : 2 : static double sphinx(const struct pl_filter_ctx *f, double x)
454 : : {
455 [ + + ]: 2 : if (x < 1e-8)
456 : : return 1.0;
457 : 1 : x *= M_PI;
458 : 1 : return 3.0 * (sin(x) - x * cos(x)) / (x * x * x);
459 : : }
460 : :
461 : : const struct pl_filter_function pl_filter_function_sphinx = {
462 : : .name = "sphinx",
463 : : .weight = sphinx,
464 : : .radius = 1.4302966531242027, // first zero
465 : : .resizable = true,
466 : : };
467 : :
468 : 110047 : static double cubic(const struct pl_filter_ctx *f, double x)
469 : : {
470 : 110047 : const double b = f->params[0], c = f->params[1];
471 : 110047 : double p0 = 6.0 - 2.0 * b,
472 : 110047 : p2 = -18.0 + 12.0 * b + 6.0 * c,
473 : 110047 : p3 = 12.0 - 9.0 * b - 6.0 * c,
474 : 110047 : q0 = 8.0 * b + 24.0 * c,
475 : 110047 : q1 = -12.0 * b - 48.0 * c,
476 : 110047 : q2 = 6.0 * b + 30.0 * c,
477 : 110047 : q3 = -b - 6.0 * c;
478 : :
479 [ + + ]: 110047 : if (x < 1.0) {
480 : 60240 : return (p0 + x * x * (p2 + x * p3)) / p0;
481 : : } else {
482 : 49807 : return (q0 + x * (q1 + x * (q2 + x * q3))) / p0;
483 : : }
484 : : }
485 : :
486 : : const struct pl_filter_function pl_filter_function_cubic = {
487 : : .name = "cubic",
488 : : .weight = cubic,
489 : : .radius = 2.0,
490 : : .params = {1.0, 0.0},
491 : : .tunable = {true, true},
492 : : };
493 : :
494 : : const struct pl_filter_function pl_filter_function_hermite = {
495 : : .name = "hermite",
496 : : .weight = cubic,
497 : : .radius = 1.0,
498 : : .params = {0.0, 0.0},
499 : : };
500 : :
501 : : const struct pl_filter_function pl_filter_function_bicubic = {
502 : : .name = "bicubic",
503 : : .weight = cubic,
504 : : .radius = 2.0,
505 : : .params = {1.0, 0.0},
506 : : .tunable = {true, true},
507 : : };
508 : :
509 : : const struct pl_filter_function pl_filter_function_bcspline = {
510 : : .name = "bcspline",
511 : : .weight = cubic,
512 : : .radius = 2.0,
513 : : .params = {1.0, 0.0},
514 : : .tunable = {true, true},
515 : : };
516 : :
517 : : const struct pl_filter_function pl_filter_function_catmull_rom = {
518 : : .name = "catmull_rom",
519 : : .weight = cubic,
520 : : .radius = 2.0,
521 : : .params = {0.0, 0.5},
522 : : .tunable = {true, true},
523 : : };
524 : :
525 : : const struct pl_filter_function pl_filter_function_mitchell = {
526 : : .name = "mitchell",
527 : : .weight = cubic,
528 : : .radius = 2.0,
529 : : .params = {1/3.0, 1/3.0},
530 : : .tunable = {true, true},
531 : : };
532 : :
533 : : const struct pl_filter_function pl_filter_function_robidoux = {
534 : : .name = "robidoux",
535 : : .weight = cubic,
536 : : .radius = 2.0,
537 : : .params = {12 / (19 + 9 * M_SQRT2), 113 / (58 + 216 * M_SQRT2)},
538 : : .tunable = {true, true},
539 : : };
540 : :
541 : : const struct pl_filter_function pl_filter_function_robidouxsharp = {
542 : : .name = "robidouxsharp",
543 : : .weight = cubic,
544 : : .radius = 2.0,
545 : : .params = {6 / (13 + 7 * M_SQRT2), 7 / (2 + 12 * M_SQRT2)},
546 : : .tunable = {true, true},
547 : : };
548 : :
549 : 30636 : static double spline16(const struct pl_filter_ctx *f, double x)
550 : : {
551 [ + + ]: 30636 : if (x < 1.0) {
552 : 15301 : return ((x - 9.0/5.0 ) * x - 1.0/5.0 ) * x + 1.0;
553 : : } else {
554 : 15335 : return ((-1.0/3.0 * (x-1) + 4.0/5.0) * (x-1) - 7.0/15.0 ) * (x-1);
555 : : }
556 : : }
557 : :
558 : : const struct pl_filter_function pl_filter_function_spline16 = {
559 : : .name = "spline16",
560 : : .weight = spline16,
561 : : .radius = 2.0,
562 : : };
563 : :
564 : 45928 : static double spline36(const struct pl_filter_ctx *f, double x)
565 : : {
566 [ + + ]: 45928 : if (x < 1.0) {
567 : 15301 : return ((13.0/11.0 * x - 453.0/209.0) * x - 3.0/209.0) * x + 1.0;
568 [ + + ]: 30627 : } else if (x < 2.0) {
569 : 15300 : return ((-6.0/11.0 * (x-1) + 270.0/209.0) * (x-1) - 156.0/ 209.0) * (x-1);
570 : : } else {
571 : 15327 : return ((1.0/11.0 * (x-2) - 45.0/209.0) * (x-2) + 26.0/209.0) * (x-2);
572 : : }
573 : : }
574 : :
575 : : const struct pl_filter_function pl_filter_function_spline36 = {
576 : : .name = "spline36",
577 : : .weight = spline36,
578 : : .radius = 3.0,
579 : : };
580 : :
581 : 61228 : static double spline64(const struct pl_filter_ctx *f, double x)
582 : : {
583 [ + + ]: 61228 : if (x < 1.0) {
584 : 15301 : return ((49.0/41.0 * x - 6387.0/2911.0) * x - 3.0/2911.0) * x + 1.0;
585 [ + + ]: 45927 : } else if (x < 2.0) {
586 : 15300 : return ((-24.0/41.0 * (x-1) + 4032.0/2911.0) * (x-1) - 2328.0/2911.0) * (x-1);
587 [ + + ]: 30627 : } else if (x < 3.0) {
588 : 15292 : return ((6.0/41.0 * (x-2) - 1008.0/2911.0) * (x-2) + 582.0/2911.0) * (x-2);
589 : : } else {
590 : 15335 : return ((-1.0/41.0 * (x-3) + 168.0/2911.0) * (x-3) - 97.0/2911.0) * (x-3);
591 : : }
592 : : }
593 : :
594 : : const struct pl_filter_function pl_filter_function_spline64 = {
595 : : .name = "spline64",
596 : : .weight = spline64,
597 : : .radius = 4.0,
598 : : };
599 : :
600 : 0 : static double oversample(const struct pl_filter_ctx *f, double x)
601 : : {
602 : 0 : return 0.0;
603 : : }
604 : :
605 : : const struct pl_filter_function pl_filter_function_oversample = {
606 : : .name = "oversample",
607 : : .weight = oversample,
608 : : .params = {0.0},
609 : : .tunable = {true},
610 : : .opaque = true,
611 : : };
612 : :
613 : : const struct pl_filter_function * const pl_filter_functions[] = {
614 : : &pl_filter_function_box,
615 : : &filter_function_dirichlet, // alias
616 : : &pl_filter_function_triangle,
617 : : &pl_filter_function_cosine,
618 : : &pl_filter_function_hann,
619 : : &filter_function_hanning, // alias
620 : : &pl_filter_function_hamming,
621 : : &pl_filter_function_welch,
622 : : &pl_filter_function_kaiser,
623 : : &pl_filter_function_blackman,
624 : : &pl_filter_function_bohman,
625 : : &pl_filter_function_gaussian,
626 : : &pl_filter_function_quadratic,
627 : : &filter_function_quadric, // alias
628 : : &pl_filter_function_sinc,
629 : : &pl_filter_function_jinc,
630 : : &pl_filter_function_sphinx,
631 : : &pl_filter_function_cubic,
632 : : &pl_filter_function_hermite,
633 : : &pl_filter_function_bicubic,
634 : : &pl_filter_function_bcspline,
635 : : &pl_filter_function_catmull_rom,
636 : : &pl_filter_function_mitchell,
637 : : &pl_filter_function_robidoux,
638 : : &pl_filter_function_robidouxsharp,
639 : : &pl_filter_function_spline16,
640 : : &pl_filter_function_spline36,
641 : : &pl_filter_function_spline64,
642 : : &pl_filter_function_oversample,
643 : : NULL,
644 : : };
645 : :
646 : : const int pl_num_filter_functions = PL_ARRAY_SIZE(pl_filter_functions) - 1;
647 : :
648 : 0 : const struct pl_filter_function *pl_find_filter_function(const char *name)
649 : : {
650 [ # # ]: 0 : if (!name)
651 : : return NULL;
652 : :
653 [ # # ]: 0 : for (int i = 0; i < pl_num_filter_functions; i++) {
654 [ # # ]: 0 : if (strcmp(name, pl_filter_functions[i]->name) == 0)
655 : 0 : return pl_filter_functions[i];
656 : : }
657 : :
658 : : return NULL;
659 : : }
660 : :
661 : : // Built-in filter function configs
662 : :
663 : : const struct pl_filter_config pl_filter_spline16 = {
664 : : .name = "spline16",
665 : : .description = "Spline (2 taps)",
666 : : .kernel = &pl_filter_function_spline16,
667 : : .allowed = PL_FILTER_ALL,
668 : : };
669 : :
670 : : const struct pl_filter_config pl_filter_spline36 = {
671 : : .name = "spline36",
672 : : .description = "Spline (3 taps)",
673 : : .kernel = &pl_filter_function_spline36,
674 : : .allowed = PL_FILTER_ALL,
675 : : };
676 : :
677 : : const struct pl_filter_config pl_filter_spline64 = {
678 : : .name = "spline64",
679 : : .description = "Spline (4 taps)",
680 : : .kernel = &pl_filter_function_spline64,
681 : : .allowed = PL_FILTER_ALL,
682 : : };
683 : :
684 : : const struct pl_filter_config pl_filter_nearest = {
685 : : .name = "nearest",
686 : : .description = "Nearest neighbor",
687 : : .kernel = &pl_filter_function_box,
688 : : .radius = 0.5,
689 : : .allowed = PL_FILTER_UPSCALING,
690 : : .recommended = PL_FILTER_UPSCALING,
691 : : };
692 : :
693 : : const struct pl_filter_config pl_filter_box = {
694 : : .name = "box",
695 : : .description = "Box averaging",
696 : : .kernel = &pl_filter_function_box,
697 : : .radius = 0.5,
698 : : .allowed = PL_FILTER_SCALING,
699 : : .recommended = PL_FILTER_DOWNSCALING,
700 : : };
701 : :
702 : : const struct pl_filter_config pl_filter_bilinear = {
703 : : .name = "bilinear",
704 : : .description = "Bilinear",
705 : : .kernel = &pl_filter_function_triangle,
706 : : .allowed = PL_FILTER_ALL,
707 : : .recommended = PL_FILTER_SCALING,
708 : : };
709 : :
710 : : const struct pl_filter_config filter_linear = {
711 : : .name = "linear",
712 : : .description = "Linear mixing",
713 : : .kernel = &pl_filter_function_triangle,
714 : : .allowed = PL_FILTER_FRAME_MIXING,
715 : : .recommended = PL_FILTER_FRAME_MIXING,
716 : : };
717 : :
718 : : static const struct pl_filter_config filter_triangle = {
719 : : .name = "triangle",
720 : : .kernel = &pl_filter_function_triangle,
721 : : .allowed = PL_FILTER_SCALING,
722 : : };
723 : :
724 : : const struct pl_filter_config pl_filter_gaussian = {
725 : : .name = "gaussian",
726 : : .description = "Gaussian",
727 : : .kernel = &pl_filter_function_gaussian,
728 : : .params = {1.0},
729 : : .allowed = PL_FILTER_ALL,
730 : : .recommended = PL_FILTER_SCALING,
731 : : };
732 : :
733 : : const struct pl_filter_config pl_filter_sinc = {
734 : : .name = "sinc",
735 : : .description = "Sinc (unwindowed)",
736 : : .kernel = &pl_filter_function_sinc,
737 : : .radius = 2.0,
738 : : .allowed = PL_FILTER_ALL,
739 : : };
740 : :
741 : : const struct pl_filter_config pl_filter_lanczos = {
742 : : .name = "lanczos",
743 : : .description = "Lanczos",
744 : : .kernel = &pl_filter_function_sinc,
745 : : .window = &pl_filter_function_sinc,
746 : : .radius = 3.0,
747 : : .allowed = PL_FILTER_ALL,
748 : : .recommended = PL_FILTER_SCALING,
749 : : };
750 : :
751 : : // See https://legacy.imagemagick.org/Usage/filter/nicolas/#upsampling
752 : : const struct pl_filter_config pl_filter_ginseng = {
753 : : .name = "ginseng",
754 : : .description = "Ginseng (Jinc-Sinc)",
755 : : .kernel = &pl_filter_function_sinc,
756 : : .window = &pl_filter_function_jinc,
757 : : .radius = 3.0,
758 : : .allowed = PL_FILTER_ALL,
759 : : };
760 : :
761 : : #define JINC_ZERO3 3.2383154841662362076499
762 : : #define JINC_ZERO4 4.2410628637960698819573
763 : :
764 : : const struct pl_filter_config pl_filter_ewa_jinc = {
765 : : .name = "ewa_jinc",
766 : : .description = "EWA Jinc (unwindowed)",
767 : : .kernel = &pl_filter_function_jinc,
768 : : .radius = JINC_ZERO3,
769 : : .polar = true,
770 : : .allowed = PL_FILTER_SCALING,
771 : : };
772 : :
773 : : const struct pl_filter_config pl_filter_ewa_lanczos = {
774 : : .name = "ewa_lanczos",
775 : : .description = "Jinc (EWA Lanczos)",
776 : : .kernel = &pl_filter_function_jinc,
777 : : .window = &pl_filter_function_jinc,
778 : : .radius = JINC_ZERO3,
779 : : .polar = true,
780 : : .allowed = PL_FILTER_SCALING,
781 : : .recommended = PL_FILTER_UPSCALING,
782 : : };
783 : :
784 : : const struct pl_filter_config pl_filter_ewa_lanczossharp = {
785 : : .name = "ewa_lanczossharp",
786 : : .description = "Sharpened Jinc",
787 : : .kernel = &pl_filter_function_jinc,
788 : : .window = &pl_filter_function_jinc,
789 : : .radius = JINC_ZERO3,
790 : : // Blur value determined by method originally developed by Nicolas
791 : : // Robidoux for Image Magick, see:
792 : : // https://www.imagemagick.org/discourse-server/viewtopic.php?p=89068#p89068
793 : : .blur = 0.98125058372237073562493,
794 : : .polar = true,
795 : : .allowed = PL_FILTER_SCALING,
796 : : .recommended = PL_FILTER_UPSCALING,
797 : : };
798 : :
799 : : const struct pl_filter_config pl_filter_ewa_lanczos4sharpest = {
800 : : .name = "ewa_lanczos4sharpest",
801 : : .description = "Sharpened Jinc-AR, 4 taps",
802 : : .kernel = &pl_filter_function_jinc,
803 : : .window = &pl_filter_function_jinc,
804 : : .radius = JINC_ZERO4,
805 : : // Similar to above, see:
806 : : // https://www.imagemagick.org/discourse-server/viewtopic.php?p=128587#p128587
807 : : .blur = 0.88451209326050047745788,
808 : : .antiring = 0.8,
809 : : .polar = true,
810 : : .allowed = PL_FILTER_SCALING,
811 : : .recommended = PL_FILTER_UPSCALING,
812 : : };
813 : :
814 : : const struct pl_filter_config pl_filter_ewa_ginseng = {
815 : : .name = "ewa_ginseng",
816 : : .description = "EWA Ginseng",
817 : : .kernel = &pl_filter_function_jinc,
818 : : .window = &pl_filter_function_sinc,
819 : : .radius = JINC_ZERO3,
820 : : .polar = true,
821 : : .allowed = PL_FILTER_SCALING,
822 : : };
823 : :
824 : : const struct pl_filter_config pl_filter_ewa_hann = {
825 : : .name = "ewa_hann",
826 : : .description = "EWA Hann",
827 : : .kernel = &pl_filter_function_jinc,
828 : : .window = &pl_filter_function_hann,
829 : : .radius = JINC_ZERO3,
830 : : .polar = true,
831 : : .allowed = PL_FILTER_SCALING,
832 : : };
833 : :
834 : : static const struct pl_filter_config filter_ewa_hanning = {
835 : : .name = "ewa_hanning",
836 : : .kernel = &pl_filter_function_jinc,
837 : : .window = &pl_filter_function_hann,
838 : : .radius = JINC_ZERO3,
839 : : .polar = true,
840 : : .allowed = PL_FILTER_SCALING,
841 : : };
842 : :
843 : : // Spline family
844 : : const struct pl_filter_config pl_filter_bicubic = {
845 : : .name = "bicubic",
846 : : .description = "Bicubic",
847 : : .kernel = &pl_filter_function_cubic,
848 : : .params = {1.0, 0.0},
849 : : .allowed = PL_FILTER_SCALING,
850 : : .recommended = PL_FILTER_SCALING,
851 : : };
852 : :
853 : : static const struct pl_filter_config filter_cubic = {
854 : : .name = "cubic",
855 : : .description = "Cubic",
856 : : .kernel = &pl_filter_function_cubic,
857 : : .params = {1.0, 0.0},
858 : : .allowed = PL_FILTER_FRAME_MIXING,
859 : : };
860 : :
861 : : const struct pl_filter_config pl_filter_hermite = {
862 : : .name = "hermite",
863 : : .description = "Hermite",
864 : : .kernel = &pl_filter_function_hermite,
865 : : .allowed = PL_FILTER_ALL,
866 : : .recommended = PL_FILTER_DOWNSCALING | PL_FILTER_FRAME_MIXING,
867 : : };
868 : :
869 : : const struct pl_filter_config pl_filter_catmull_rom = {
870 : : .name = "catmull_rom",
871 : : .description = "Catmull-Rom",
872 : : .kernel = &pl_filter_function_cubic,
873 : : .params = {0.0, 0.5},
874 : : .allowed = PL_FILTER_ALL,
875 : : .recommended = PL_FILTER_SCALING,
876 : : };
877 : :
878 : : const struct pl_filter_config pl_filter_mitchell = {
879 : : .name = "mitchell",
880 : : .description = "Mitchell-Netravali",
881 : : .kernel = &pl_filter_function_cubic,
882 : : .params = {1/3.0, 1/3.0},
883 : : .allowed = PL_FILTER_ALL,
884 : : .recommended = PL_FILTER_DOWNSCALING,
885 : : };
886 : :
887 : : const struct pl_filter_config pl_filter_mitchell_clamp = {
888 : : .name = "mitchell_clamp",
889 : : .description = "Mitchell (clamped)",
890 : : .kernel = &pl_filter_function_cubic,
891 : : .params = {1/3.0, 1/3.0},
892 : : .clamp = 1.0,
893 : : .allowed = PL_FILTER_ALL,
894 : : };
895 : :
896 : : const struct pl_filter_config pl_filter_robidoux = {
897 : : .name = "robidoux",
898 : : .description = "Robidoux",
899 : : .kernel = &pl_filter_function_cubic,
900 : : .params = {12 / (19 + 9 * M_SQRT2), 113 / (58 + 216 * M_SQRT2)},
901 : : .allowed = PL_FILTER_ALL,
902 : : };
903 : :
904 : : const struct pl_filter_config pl_filter_robidouxsharp = {
905 : : .name = "robidouxsharp",
906 : : .description = "RobidouxSharp",
907 : : .kernel = &pl_filter_function_cubic,
908 : : .params = {6 / (13 + 7 * M_SQRT2), 7 / (2 + 12 * M_SQRT2)},
909 : : .allowed = PL_FILTER_ALL,
910 : : };
911 : :
912 : : const struct pl_filter_config pl_filter_ewa_robidoux = {
913 : : .name = "ewa_robidoux",
914 : : .description = "EWA Robidoux",
915 : : .kernel = &pl_filter_function_cubic,
916 : : .params = {12 / (19 + 9 * M_SQRT2), 113 / (58 + 216 * M_SQRT2)},
917 : : .polar = true,
918 : : .allowed = PL_FILTER_SCALING,
919 : : };
920 : :
921 : : const struct pl_filter_config pl_filter_ewa_robidouxsharp = {
922 : : .name = "ewa_robidouxsharp",
923 : : .description = "EWA RobidouxSharp",
924 : : .kernel = &pl_filter_function_cubic,
925 : : .params = {6 / (13 + 7 * M_SQRT2), 7 / (2 + 12 * M_SQRT2)},
926 : : .polar = true,
927 : : .allowed = PL_FILTER_SCALING,
928 : : };
929 : :
930 : : const struct pl_filter_config pl_filter_oversample = {
931 : : .name = "oversample",
932 : : .description = "Oversampling",
933 : : .kernel = &pl_filter_function_oversample,
934 : : .params = {0.0},
935 : : .allowed = PL_FILTER_UPSCALING | PL_FILTER_FRAME_MIXING,
936 : : .recommended = PL_FILTER_UPSCALING | PL_FILTER_FRAME_MIXING,
937 : : };
938 : :
939 : : const struct pl_filter_config * const pl_filter_configs[] = {
940 : : // Sorted roughly in terms of priority / relevance
941 : : &pl_filter_bilinear,
942 : : &filter_triangle, // alias
943 : : &filter_linear, // pseudo-alias (frame mixing only)
944 : : &pl_filter_nearest,
945 : : &pl_filter_spline16,
946 : : &pl_filter_spline36,
947 : : &pl_filter_spline64,
948 : : &pl_filter_lanczos,
949 : : &pl_filter_ewa_lanczos,
950 : : &pl_filter_ewa_lanczossharp,
951 : : &pl_filter_ewa_lanczos4sharpest,
952 : : &pl_filter_bicubic,
953 : : &filter_cubic, // pseudo-alias (frame mixing only)
954 : : &pl_filter_hermite,
955 : : &pl_filter_gaussian,
956 : : &pl_filter_oversample,
957 : : &pl_filter_mitchell,
958 : : &pl_filter_mitchell_clamp,
959 : : &pl_filter_sinc,
960 : : &pl_filter_ginseng,
961 : : &pl_filter_ewa_jinc,
962 : : &pl_filter_ewa_ginseng,
963 : : &pl_filter_ewa_hann,
964 : : &filter_ewa_hanning, // alias
965 : : &pl_filter_catmull_rom,
966 : : &pl_filter_robidoux,
967 : : &pl_filter_robidouxsharp,
968 : : &pl_filter_ewa_robidoux,
969 : : &pl_filter_ewa_robidouxsharp,
970 : :
971 : : NULL,
972 : : };
973 : :
974 : : const int pl_num_filter_configs = PL_ARRAY_SIZE(pl_filter_configs) - 1;
975 : :
976 : : const struct pl_filter_config *
977 : 0 : pl_find_filter_config(const char *name, enum pl_filter_usage usage)
978 : : {
979 [ # # ]: 0 : if (!name)
980 : : return NULL;
981 : :
982 [ # # ]: 0 : for (int i = 0; i < pl_num_filter_configs; i++) {
983 [ # # ]: 0 : if ((pl_filter_configs[i]->allowed & usage) != usage)
984 : 0 : continue;
985 [ # # ]: 0 : if (strcmp(name, pl_filter_configs[i]->name) == 0)
986 : 0 : return pl_filter_configs[i];
987 : : }
988 : :
989 : : return NULL;
990 : : }
991 : :
992 : : // Backwards compatibility with older API
993 : :
994 : : const struct pl_filter_function_preset pl_filter_function_presets[] = {
995 : : {"none", NULL},
996 : : {"box", &pl_filter_function_box},
997 : : {"dirichlet", &filter_function_dirichlet}, // alias
998 : : {"triangle", &pl_filter_function_triangle},
999 : : {"cosine", &pl_filter_function_cosine},
1000 : : {"hann", &pl_filter_function_hann},
1001 : : {"hanning", &filter_function_hanning}, // alias
1002 : : {"hamming", &pl_filter_function_hamming},
1003 : : {"welch", &pl_filter_function_welch},
1004 : : {"kaiser", &pl_filter_function_kaiser},
1005 : : {"blackman", &pl_filter_function_blackman},
1006 : : {"bohman", &pl_filter_function_bohman},
1007 : : {"gaussian", &pl_filter_function_gaussian},
1008 : : {"quadratic", &pl_filter_function_quadratic},
1009 : : {"quadric", &filter_function_quadric}, // alias
1010 : : {"sinc", &pl_filter_function_sinc},
1011 : : {"jinc", &pl_filter_function_jinc},
1012 : : {"sphinx", &pl_filter_function_sphinx},
1013 : : {"cubic", &pl_filter_function_cubic},
1014 : : {"hermite", &pl_filter_function_hermite},
1015 : : {"bicubic", &pl_filter_function_bicubic},
1016 : : {"bcspline", &pl_filter_function_bcspline},
1017 : : {"catmull_rom", &pl_filter_function_catmull_rom}, // alias
1018 : : {"mitchell", &pl_filter_function_mitchell},
1019 : : {"robidoux", &pl_filter_function_robidoux},
1020 : : {"robidouxsharp", &pl_filter_function_robidouxsharp},
1021 : : {"spline16", &pl_filter_function_spline16},
1022 : : {"spline36", &pl_filter_function_spline36},
1023 : : {"spline64", &pl_filter_function_spline64},
1024 : : {0},
1025 : : };
1026 : :
1027 : : const int pl_num_filter_function_presets = PL_ARRAY_SIZE(pl_filter_function_presets) - 1;
1028 : :
1029 : 0 : const struct pl_filter_function_preset *pl_find_filter_function_preset(const char *name)
1030 : : {
1031 [ # # ]: 0 : if (!name)
1032 : : return NULL;
1033 : :
1034 [ # # ]: 0 : for (int i = 0; pl_filter_function_presets[i].name; i++) {
1035 [ # # ]: 0 : if (strcmp(pl_filter_function_presets[i].name, name) == 0)
1036 : 0 : return &pl_filter_function_presets[i];
1037 : : }
1038 : :
1039 : : return NULL;
1040 : : }
1041 : :
1042 : 0 : const struct pl_filter_preset *pl_find_filter_preset(const char *name)
1043 : : {
1044 [ # # ]: 0 : if (!name)
1045 : : return NULL;
1046 : :
1047 [ # # ]: 0 : for (int i = 0; pl_filter_presets[i].name; i++) {
1048 [ # # ]: 0 : if (strcmp(pl_filter_presets[i].name, name) == 0)
1049 : 0 : return &pl_filter_presets[i];
1050 : : }
1051 : :
1052 : : return NULL;
1053 : : }
1054 : :
1055 : : const struct pl_filter_preset pl_filter_presets[] = {
1056 : : {"none", NULL, "Built-in sampling"},
1057 : : COMMON_FILTER_PRESETS,
1058 : : {0}
1059 : : };
1060 : :
1061 : : const int pl_num_filter_presets = PL_ARRAY_SIZE(pl_filter_presets) - 1;
|