/* * This file is part of libplacebo. * * libplacebo is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * libplacebo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with libplacebo. If not, see . */ /* * Some of the filter code originally derives (via mpv) from Glumpy: * # Copyright (c) 2009-2016 Nicolas P. Rougier. All rights reserved. * # Distributed under the (new) BSD License. * (https://github.com/glumpy/glumpy/blob/master/glumpy/library/build-spatial-filters.py) * * The math underlying each filter function was written from scratch, with * some algorithms coming from a number of different sources, including: * - https://en.wikipedia.org/wiki/Window_function * - https://en.wikipedia.org/wiki/Jinc * - http://vector-agg.cvs.sourceforge.net/viewvc/vector-agg/agg-2.5/include/agg_image_filters.h * - Vapoursynth plugin fmtconv (WTFPL Licensed), which is based on * dither plugin for avisynth from the same author: * https://github.com/vapoursynth/fmtconv/tree/master/src/fmtc * - Paul Heckbert's "zoom" * - XBMC: ConvolutionKernels.cpp etc. * - https://github.com/AviSynth/jinc-resize (only used to verify the math) */ #include #include "common.h" #include "filters.h" #include "log.h" #ifdef PL_HAVE_WIN32 #define j1 _j1 #endif bool pl_filter_function_eq(const struct pl_filter_function *a, const struct pl_filter_function *b) { return (a ? a->weight : NULL) == (b ? b->weight : NULL); } bool pl_filter_config_eq(const struct pl_filter_config *a, const struct pl_filter_config *b) { if (!a || !b) return a == b; bool eq = pl_filter_function_eq(a->kernel, b->kernel) && pl_filter_function_eq(a->window, b->window) && a->radius == b->radius && a->clamp == b->clamp && a->blur == b->blur && a->taper == b->taper && a->polar == b->polar && a->antiring == b->antiring; for (int i = 0; i < PL_FILTER_MAX_PARAMS; i++) { if (a->kernel->tunable[i]) eq &= a->params[i] == b->params[i]; if (a->window && a->window->tunable[i]) eq &= a->wparams[i] == b->wparams[i]; } return eq; } double pl_filter_sample(const struct pl_filter_config *c, double x) { const float radius = pl_filter_radius_bound(c); // All filters are symmetric, and in particular only need to be defined // for [0, radius]. x = fabs(x); // Return early for values outside of the kernel radius, since the functions // are not necessarily valid outside of this interval. No such check is // needed for the window, because it's always stretched to fit. if (x > radius) return 0.0; // Apply the blur and taper coefficients as needed double kx = x <= c->taper ? 0.0 : (x - c->taper) / (1.0 - c->taper / radius); if (c->blur > 0.0) kx /= c->blur; pl_assert(!c->kernel->opaque); double k = c->kernel->weight(&(const struct pl_filter_ctx) { .radius = radius, .params = { c->kernel->tunable[0] ? c->params[0] : c->kernel->params[0], c->kernel->tunable[1] ? c->params[1] : c->kernel->params[1], }, }, kx); // Apply the optional windowing function if (c->window) { pl_assert(!c->window->opaque); double wx = x / radius * c->window->radius; k *= c->window->weight(&(struct pl_filter_ctx) { .radius = c->window->radius, .params = { c->window->tunable[0] ? c->wparams[0] : c->window->params[0], c->window->tunable[1] ? c->wparams[1] : c->window->params[1], }, }, wx); } return k < 0 ? (1 - c->clamp) * k : k; } static void filter_cutoffs(const struct pl_filter_config *c, float cutoff, float *out_radius, float *out_radius_zero) { const float bound = pl_filter_radius_bound(c); float prev = 0.0, fprev = pl_filter_sample(c, prev); bool found_root = false; const float step = 1e-2f; for (float x = 0.0; x < bound + step; x += step) { float fx = pl_filter_sample(c, x); if ((fprev > cutoff && fx <= cutoff) || (fprev < -cutoff && fx >= -cutoff)) { // Found zero crossing float root = x - fx * (x - prev) / (fx - fprev); // secant method root = fminf(root, bound); *out_radius = root; if (!found_root) // first root *out_radius_zero = root; found_root = true; } prev = x; fprev = fx; } if (!found_root) *out_radius_zero = *out_radius = bound; } // Compute a single row of weights for a given filter in one dimension, indexed // by the indicated subpixel offset. Writes `f->row_size` values to `out`. static void compute_row(struct pl_filter_t *f, double offset, float *out) { double wsum = 0.0; for (int i = 0; i < f->row_size; i++) { // For the example of a filter with row size 4 and offset 0.3, we have: // // 0 1 * 2 3 // // * indicates the sampled position. What we want to compute is the // distance from each index to that sampled position. pl_assert(f->row_size % 2 == 0); const int base = f->row_size / 2 - 1; // index to the left of the center const double center = base + offset; // offset of center relative to idx 0 double w = pl_filter_sample(&f->params.config, i - center); out[i] = w; wsum += w; } // Readjust weights to preserve energy pl_assert(wsum > 0); for (int i = 0; i < f->row_size; i++) out[i] /= wsum; } // Needed for backwards compatibility with v1 configuration API static struct pl_filter_function *dupfilter(void *alloc, const struct pl_filter_function *f) { return f ? pl_memdup(alloc, (void *)f, sizeof(*f)) : NULL; } pl_filter pl_filter_generate(pl_log log, const struct pl_filter_params *params) { pl_assert(params); if (params->lut_entries <= 0 || !params->config.kernel) { pl_fatal(log, "Invalid params: missing lut_entries or config.kernel"); return NULL; } if (params->config.kernel->opaque) { pl_err(log, "Trying to use opaque kernel '%s' in non-opaque context!", params->config.kernel->name); return NULL; } if (params->config.window && params->config.window->opaque) { pl_err(log, "Trying to use opaque window '%s' in non-opaque context!", params->config.window->name); return NULL; } struct pl_filter_t *f = pl_zalloc_ptr(NULL, f); f->params = *params; f->params.config.kernel = dupfilter(f, params->config.kernel); f->params.config.window = dupfilter(f, params->config.window); // Compute main lobe and total filter size filter_cutoffs(¶ms->config, params->cutoff, &f->radius, &f->radius_zero); f->radius_cutoff = f->radius; // backwards compatibility float *weights; if (params->config.polar) { // Compute a 1D array indexed by radius weights = pl_alloc(f, params->lut_entries * sizeof(float)); for (int i = 0; i < params->lut_entries; i++) { double x = f->radius * i / (params->lut_entries - 1); weights[i] = pl_filter_sample(¶ms->config, x); } } else { // Pick the most appropriate row size f->row_size = ceilf(f->radius) * 2; if (params->max_row_size && f->row_size > params->max_row_size) { pl_info(log, "Required filter size %d exceeds the maximum allowed " "size of %d. This may result in adverse effects (aliasing, " "or moirĂ© artifacts).", f->row_size, params->max_row_size); f->row_size = params->max_row_size; f->insufficient = true; } f->row_stride = PL_ALIGN(f->row_size, params->row_stride_align); // Compute a 2D array indexed by the subpixel position weights = pl_calloc(f, params->lut_entries * f->row_stride, sizeof(float)); for (int i = 0; i < params->lut_entries; i++) { compute_row(f, i / (double)(params->lut_entries - 1), weights + f->row_stride * i); } } f->weights = weights; return f; } void pl_filter_free(pl_filter *filter) { pl_free_ptr((void **) filter); } // Built-in filter functions static double box(const struct pl_filter_ctx *f, double x) { return 1.0; } const struct pl_filter_function pl_filter_function_box = { .weight = box, .name = "box", .radius = 1.0, .resizable = true, }; static const struct pl_filter_function filter_function_dirichlet = { .name = "dirichlet", // alias .weight = box, .radius = 1.0, .resizable = true, }; static double triangle(const struct pl_filter_ctx *f, double x) { return 1.0 - x / f->radius; } const struct pl_filter_function pl_filter_function_triangle = { .name = "triangle", .weight = triangle, .radius = 1.0, .resizable = true, }; static double cosine(const struct pl_filter_ctx *f, double x) { return cos(x); } const struct pl_filter_function pl_filter_function_cosine = { .name = "cosine", .weight = cosine, .radius = M_PI / 2.0, }; static double hann(const struct pl_filter_ctx *f, double x) { return 0.5 + 0.5 * cos(M_PI * x); } const struct pl_filter_function pl_filter_function_hann = { .name = "hann", .weight = hann, .radius = 1.0, }; static const struct pl_filter_function filter_function_hanning = { .name = "hanning", // alias .weight = hann, .radius = 1.0, }; static double hamming(const struct pl_filter_ctx *f, double x) { return 0.54 + 0.46 * cos(M_PI * x); } const struct pl_filter_function pl_filter_function_hamming = { .name = "hamming", .weight = hamming, .radius = 1.0, }; static double welch(const struct pl_filter_ctx *f, double x) { return 1.0 - x * x; } const struct pl_filter_function pl_filter_function_welch = { .name = "welch", .weight = welch, .radius = 1.0, }; static double bessel_i0(double x) { double s = 1.0; double y = x * x / 4.0; double t = y; int i = 2; while (t > 1e-12) { s += t; t *= y / (i * i); i += 1; } return s; } static double kaiser(const struct pl_filter_ctx *f, double x) { double alpha = fmax(f->params[0], 0.0); double scale = bessel_i0(alpha); return bessel_i0(alpha * sqrt(1.0 - x * x)) / scale; } const struct pl_filter_function pl_filter_function_kaiser = { .name = "kaiser", .weight = kaiser, .radius = 1.0, .params = {2.0}, .tunable = {true}, }; static double blackman(const struct pl_filter_ctx *f, double x) { double a = f->params[0]; double a0 = (1 - a) / 2.0, a1 = 1 / 2.0, a2 = a / 2.0; x *= M_PI; return a0 + a1 * cos(x) + a2 * cos(2 * x); } const struct pl_filter_function pl_filter_function_blackman = { .name = "blackman", .weight = blackman, .radius = 1.0, .params = {0.16}, .tunable = {true}, }; static double bohman(const struct pl_filter_ctx *f, double x) { double pix = M_PI * x; return (1.0 - x) * cos(pix) + sin(pix) / M_PI; } const struct pl_filter_function pl_filter_function_bohman = { .name = "bohman", .weight = bohman, .radius = 1.0, }; static double gaussian(const struct pl_filter_ctx *f, double x) { return exp(-2.0 * x * x / f->params[0]); } const struct pl_filter_function pl_filter_function_gaussian = { .name = "gaussian", .weight = gaussian, .radius = 2.0, .resizable = true, .params = {1.0}, .tunable = {true}, }; static double quadratic(const struct pl_filter_ctx *f, double x) { if (x < 0.5) { return 1.0 - 4.0/3.0 * (x * x); } else { return 2.0 / 3.0 * (x - 1.5) * (x - 1.5); } } const struct pl_filter_function pl_filter_function_quadratic = { .name = "quadratic", .weight = quadratic, .radius = 1.5, }; static const struct pl_filter_function filter_function_quadric = { .name = "quadric", // alias .weight = quadratic, .radius = 1.5, }; static double sinc(const struct pl_filter_ctx *f, double x) { if (x < 1e-8) return 1.0; x *= M_PI; return sin(x) / x; } const struct pl_filter_function pl_filter_function_sinc = { .name = "sinc", .weight = sinc, .radius = 1.0, .resizable = true, }; static double jinc(const struct pl_filter_ctx *f, double x) { if (x < 1e-8) return 1.0; x *= M_PI; return 2.0 * j1(x) / x; } const struct pl_filter_function pl_filter_function_jinc = { .name = "jinc", .weight = jinc, .radius = 1.2196698912665045, // first zero .resizable = true, }; static double sphinx(const struct pl_filter_ctx *f, double x) { if (x < 1e-8) return 1.0; x *= M_PI; return 3.0 * (sin(x) - x * cos(x)) / (x * x * x); } const struct pl_filter_function pl_filter_function_sphinx = { .name = "sphinx", .weight = sphinx, .radius = 1.4302966531242027, // first zero .resizable = true, }; static double cubic(const struct pl_filter_ctx *f, double x) { const double b = f->params[0], c = f->params[1]; double p0 = 6.0 - 2.0 * b, p2 = -18.0 + 12.0 * b + 6.0 * c, p3 = 12.0 - 9.0 * b - 6.0 * c, q0 = 8.0 * b + 24.0 * c, q1 = -12.0 * b - 48.0 * c, q2 = 6.0 * b + 30.0 * c, q3 = -b - 6.0 * c; if (x < 1.0) { return (p0 + x * x * (p2 + x * p3)) / p0; } else { return (q0 + x * (q1 + x * (q2 + x * q3))) / p0; } } const struct pl_filter_function pl_filter_function_cubic = { .name = "cubic", .weight = cubic, .radius = 2.0, .params = {1.0, 0.0}, .tunable = {true, true}, }; static const struct pl_filter_function filter_function_bicubic = { .name = "bicubic", // alias .weight = cubic, .radius = 2.0, .params = {1.0, 0.0}, .tunable = {true, true}, }; static const struct pl_filter_function filter_function_bcspline = { .name = "bcspline", // alias .weight = cubic, .radius = 2.0, .params = {1.0, 0.0}, .tunable = {true, true}, }; const struct pl_filter_function pl_filter_function_hermite = { .name = "hermite", .weight = cubic, .radius = 1.0, .params = {0.0, 0.0}, }; static double spline16(const struct pl_filter_ctx *f, double x) { if (x < 1.0) { return ((x - 9.0/5.0 ) * x - 1.0/5.0 ) * x + 1.0; } else { return ((-1.0/3.0 * (x-1) + 4.0/5.0) * (x-1) - 7.0/15.0 ) * (x-1); } } const struct pl_filter_function pl_filter_function_spline16 = { .name = "spline16", .weight = spline16, .radius = 2.0, }; static double spline36(const struct pl_filter_ctx *f, double x) { if (x < 1.0) { return ((13.0/11.0 * x - 453.0/209.0) * x - 3.0/209.0) * x + 1.0; } else if (x < 2.0) { return ((-6.0/11.0 * (x-1) + 270.0/209.0) * (x-1) - 156.0/ 209.0) * (x-1); } else { return ((1.0/11.0 * (x-2) - 45.0/209.0) * (x-2) + 26.0/209.0) * (x-2); } } const struct pl_filter_function pl_filter_function_spline36 = { .name = "spline36", .weight = spline36, .radius = 3.0, }; static double spline64(const struct pl_filter_ctx *f, double x) { if (x < 1.0) { return ((49.0/41.0 * x - 6387.0/2911.0) * x - 3.0/2911.0) * x + 1.0; } else if (x < 2.0) { return ((-24.0/41.0 * (x-1) + 4032.0/2911.0) * (x-1) - 2328.0/2911.0) * (x-1); } else if (x < 3.0) { return ((6.0/41.0 * (x-2) - 1008.0/2911.0) * (x-2) + 582.0/2911.0) * (x-2); } else { return ((-1.0/41.0 * (x-3) + 168.0/2911.0) * (x-3) - 97.0/2911.0) * (x-3); } } const struct pl_filter_function pl_filter_function_spline64 = { .name = "spline64", .weight = spline64, .radius = 4.0, }; static double oversample(const struct pl_filter_ctx *f, double x) { return 0.0; } const struct pl_filter_function pl_filter_function_oversample = { .name = "oversample", .weight = oversample, .params = {0.0}, .tunable = {true}, .opaque = true, }; const struct pl_filter_function * const pl_filter_functions[] = { &pl_filter_function_box, &filter_function_dirichlet, // alias &pl_filter_function_triangle, &pl_filter_function_cosine, &pl_filter_function_hann, &filter_function_hanning, // alias &pl_filter_function_hamming, &pl_filter_function_welch, &pl_filter_function_kaiser, &pl_filter_function_blackman, &pl_filter_function_bohman, &pl_filter_function_gaussian, &pl_filter_function_quadratic, &filter_function_quadric, // alias &pl_filter_function_sinc, &pl_filter_function_jinc, &pl_filter_function_sphinx, &pl_filter_function_cubic, &filter_function_bicubic, // alias &filter_function_bcspline, // alias &pl_filter_function_hermite, &pl_filter_function_spline16, &pl_filter_function_spline36, &pl_filter_function_spline64, &pl_filter_function_oversample, NULL, }; const int pl_num_filter_functions = PL_ARRAY_SIZE(pl_filter_functions) - 1; const struct pl_filter_function *pl_find_filter_function(const char *name) { if (!name) return NULL; for (int i = 0; i < pl_num_filter_functions; i++) { if (strcmp(name, pl_filter_functions[i]->name) == 0) return pl_filter_functions[i]; } return NULL; } // Built-in filter function configs const struct pl_filter_config pl_filter_spline16 = { .name = "spline16", .description = "Spline (2 taps)", .kernel = &pl_filter_function_spline16, .allowed = PL_FILTER_ALL, }; const struct pl_filter_config pl_filter_spline36 = { .name = "spline36", .description = "Spline (3 taps)", .kernel = &pl_filter_function_spline36, .allowed = PL_FILTER_ALL, }; const struct pl_filter_config pl_filter_spline64 = { .name = "spline64", .description = "Spline (4 taps)", .kernel = &pl_filter_function_spline64, .allowed = PL_FILTER_ALL, }; const struct pl_filter_config pl_filter_nearest = { .name = "nearest", .description = "Nearest neighbor", .kernel = &pl_filter_function_box, .radius = 0.5, .allowed = PL_FILTER_UPSCALING, .recommended = PL_FILTER_UPSCALING, }; const struct pl_filter_config pl_filter_box = { .name = "box", .description = "Box averaging", .kernel = &pl_filter_function_box, .radius = 0.5, .allowed = PL_FILTER_SCALING, .recommended = PL_FILTER_DOWNSCALING, }; const struct pl_filter_config pl_filter_bilinear = { .name = "bilinear", .description = "Bilinear", .kernel = &pl_filter_function_triangle, .allowed = PL_FILTER_ALL, .recommended = PL_FILTER_SCALING, }; const struct pl_filter_config filter_linear = { .name = "linear", .description = "Linear mixing", .kernel = &pl_filter_function_triangle, .allowed = PL_FILTER_FRAME_MIXING, .recommended = PL_FILTER_FRAME_MIXING, }; static const struct pl_filter_config filter_triangle = { .name = "triangle", .kernel = &pl_filter_function_triangle, .allowed = PL_FILTER_SCALING, }; const struct pl_filter_config pl_filter_gaussian = { .name = "gaussian", .description = "Gaussian", .kernel = &pl_filter_function_gaussian, .params = {1.0}, .allowed = PL_FILTER_ALL, .recommended = PL_FILTER_SCALING, }; const struct pl_filter_config pl_filter_sinc = { .name = "sinc", .description = "Sinc (unwindowed)", .kernel = &pl_filter_function_sinc, .radius = 3.0, .allowed = PL_FILTER_ALL, }; const struct pl_filter_config pl_filter_lanczos = { .name = "lanczos", .description = "Lanczos", .kernel = &pl_filter_function_sinc, .window = &pl_filter_function_sinc, .radius = 3.0, .allowed = PL_FILTER_ALL, .recommended = PL_FILTER_SCALING, }; const struct pl_filter_config pl_filter_ginseng = { .name = "ginseng", .description = "Ginseng (Jinc-Sinc)", .kernel = &pl_filter_function_sinc, .window = &pl_filter_function_jinc, .radius = 3.0, .allowed = PL_FILTER_ALL, }; #define JINC_ZERO3 3.2383154841662362076499 #define JINC_ZERO4 4.2410628637960698819573 const struct pl_filter_config pl_filter_ewa_jinc = { .name = "ewa_jinc", .description = "EWA Jinc (unwindowed)", .kernel = &pl_filter_function_jinc, .radius = JINC_ZERO3, .polar = true, .allowed = PL_FILTER_SCALING, }; const struct pl_filter_config pl_filter_ewa_lanczos = { .name = "ewa_lanczos", .description = "Jinc (EWA Lanczos)", .kernel = &pl_filter_function_jinc, .window = &pl_filter_function_jinc, .radius = JINC_ZERO3, .polar = true, .allowed = PL_FILTER_SCALING, .recommended = PL_FILTER_UPSCALING, }; const struct pl_filter_config pl_filter_ewa_lanczossharp = { .name = "ewa_lanczossharp", .description = "Sharpened Jinc", .kernel = &pl_filter_function_jinc, .window = &pl_filter_function_jinc, .radius = JINC_ZERO3, .blur = 0.98125058372237073562493, .polar = true, .allowed = PL_FILTER_SCALING, .recommended = PL_FILTER_UPSCALING, }; const struct pl_filter_config pl_filter_ewa_lanczos4sharpest = { .name = "ewa_lanczos4sharpest", .description = "Sharpened Jinc-AR, 4 taps", .kernel = &pl_filter_function_jinc, .window = &pl_filter_function_jinc, .radius = JINC_ZERO4, .blur = 0.88451209326050047745788, .antiring = 0.8, .polar = true, .allowed = PL_FILTER_SCALING, .recommended = PL_FILTER_UPSCALING, }; const struct pl_filter_config pl_filter_ewa_ginseng = { .name = "ewa_ginseng", .description = "EWA Ginseng", .kernel = &pl_filter_function_jinc, .window = &pl_filter_function_sinc, .radius = JINC_ZERO3, .polar = true, .allowed = PL_FILTER_SCALING, }; const struct pl_filter_config pl_filter_ewa_hann = { .name = "ewa_hann", .description = "EWA Hann", .kernel = &pl_filter_function_jinc, .window = &pl_filter_function_hann, .radius = JINC_ZERO3, .polar = true, .allowed = PL_FILTER_SCALING, }; static const struct pl_filter_config filter_ewa_hanning = { .name = "ewa_hanning", .kernel = &pl_filter_function_jinc, .window = &pl_filter_function_hann, .radius = JINC_ZERO3, .polar = true, .allowed = PL_FILTER_SCALING, }; // Spline family const struct pl_filter_config pl_filter_bicubic = { .name = "bicubic", .description = "Bicubic", .kernel = &pl_filter_function_cubic, .params = {1.0, 0.0}, .allowed = PL_FILTER_SCALING, .recommended = PL_FILTER_SCALING, }; static const struct pl_filter_config filter_cubic = { .name = "cubic", .description = "Cubic", .kernel = &pl_filter_function_cubic, .params = {1.0, 0.0}, .allowed = PL_FILTER_FRAME_MIXING, }; const struct pl_filter_config pl_filter_hermite = { .name = "hermite", .description = "Hermite", .kernel = &pl_filter_function_hermite, .allowed = PL_FILTER_ALL, .recommended = PL_FILTER_DOWNSCALING | PL_FILTER_FRAME_MIXING, }; const struct pl_filter_config pl_filter_catmull_rom = { .name = "catmull_rom", .description = "Catmull-Rom", .kernel = &pl_filter_function_cubic, .params = {0.0, 0.5}, .allowed = PL_FILTER_ALL, .recommended = PL_FILTER_SCALING, }; const struct pl_filter_config pl_filter_mitchell = { .name = "mitchell", .description = "Mitchell-Netravali", .kernel = &pl_filter_function_cubic, .params = {1/3.0, 1/3.0}, .allowed = PL_FILTER_ALL, .recommended = PL_FILTER_DOWNSCALING, }; const struct pl_filter_config pl_filter_mitchell_clamp = { .name = "mitchell_clamp", .description = "Mitchell (clamped)", .kernel = &pl_filter_function_cubic, .params = {1/3.0, 1/3.0}, .clamp = 1.0, .allowed = PL_FILTER_ALL, }; const struct pl_filter_config pl_filter_robidoux = { .name = "robidoux", .description = "Robidoux", .kernel = &pl_filter_function_cubic, .params = {12 / (19 + 9 * M_SQRT2), 113 / (58 + 216 * M_SQRT2)}, .allowed = PL_FILTER_ALL, }; const struct pl_filter_config pl_filter_robidouxsharp = { .name = "robidouxsharp", .description = "RobidouxSharp", .kernel = &pl_filter_function_cubic, .params = {6 / (13 + 7 * M_SQRT2), 7 / (2 + 12 * M_SQRT2)}, .allowed = PL_FILTER_ALL, }; const struct pl_filter_config pl_filter_ewa_robidoux = { .name = "ewa_robidoux", .description = "EWA Robidoux", .kernel = &pl_filter_function_cubic, .params = {12 / (19 + 9 * M_SQRT2), 113 / (58 + 216 * M_SQRT2)}, .polar = true, .allowed = PL_FILTER_SCALING, }; const struct pl_filter_config pl_filter_ewa_robidouxsharp = { .name = "ewa_robidouxsharp", .description = "EWA RobidouxSharp", .kernel = &pl_filter_function_cubic, .params = {6 / (13 + 7 * M_SQRT2), 7 / (2 + 12 * M_SQRT2)}, .polar = true, .allowed = PL_FILTER_SCALING, }; const struct pl_filter_config pl_filter_oversample = { .name = "oversample", .description = "Oversampling", .kernel = &pl_filter_function_oversample, .params = {0.0}, .allowed = PL_FILTER_UPSCALING | PL_FILTER_FRAME_MIXING, .recommended = PL_FILTER_UPSCALING | PL_FILTER_FRAME_MIXING, }; const struct pl_filter_config * const pl_filter_configs[] = { // Sorted roughly in terms of priority / relevance &pl_filter_bilinear, &filter_triangle, // alias &filter_linear, // pseudo-alias (frame mixing only) &pl_filter_nearest, &pl_filter_spline16, &pl_filter_spline36, &pl_filter_spline64, &pl_filter_lanczos, &pl_filter_ewa_lanczos, &pl_filter_ewa_lanczossharp, &pl_filter_ewa_lanczos4sharpest, &pl_filter_bicubic, &filter_cubic, // pseudo-alias (frame mixing only) &pl_filter_hermite, &pl_filter_gaussian, &pl_filter_oversample, &pl_filter_mitchell, &pl_filter_mitchell_clamp, &pl_filter_sinc, &pl_filter_ginseng, &pl_filter_ewa_jinc, &pl_filter_ewa_ginseng, &pl_filter_ewa_hann, &filter_ewa_hanning, // alias &pl_filter_catmull_rom, &pl_filter_robidoux, &pl_filter_robidouxsharp, &pl_filter_ewa_robidoux, &pl_filter_ewa_robidouxsharp, NULL, }; const int pl_num_filter_configs = PL_ARRAY_SIZE(pl_filter_configs) - 1; const struct pl_filter_config * pl_find_filter_config(const char *name, enum pl_filter_usage usage) { if (!name) return NULL; for (int i = 0; i < pl_num_filter_configs; i++) { if ((pl_filter_configs[i]->allowed & usage) != usage) continue; if (strcmp(name, pl_filter_configs[i]->name) == 0) return pl_filter_configs[i]; } return NULL; } // Backwards compatibility with older API const struct pl_filter_function_preset pl_filter_function_presets[] = { {"none", NULL}, {"box", &pl_filter_function_box}, {"dirichlet", &filter_function_dirichlet}, // alias {"triangle", &pl_filter_function_triangle}, {"cosine", &pl_filter_function_cosine}, {"hann", &pl_filter_function_hann}, {"hanning", &filter_function_hanning}, // alias {"hamming", &pl_filter_function_hamming}, {"welch", &pl_filter_function_welch}, {"kaiser", &pl_filter_function_kaiser}, {"blackman", &pl_filter_function_blackman}, {"bohman", &pl_filter_function_bohman}, {"gaussian", &pl_filter_function_gaussian}, {"quadratic", &pl_filter_function_quadratic}, {"quadric", &filter_function_quadric}, // alias {"sinc", &pl_filter_function_sinc}, {"jinc", &pl_filter_function_jinc}, {"sphinx", &pl_filter_function_sphinx}, {"cubic", &pl_filter_function_cubic}, {"bicubic", &filter_function_bicubic}, // alias {"bcspline", &filter_function_bcspline}, // alias {"hermite", &pl_filter_function_hermite}, {"spline16", &pl_filter_function_spline16}, {"spline36", &pl_filter_function_spline36}, {"spline64", &pl_filter_function_spline64}, {0}, }; const int pl_num_filter_function_presets = PL_ARRAY_SIZE(pl_filter_function_presets) - 1; const struct pl_filter_function_preset *pl_find_filter_function_preset(const char *name) { if (!name) return NULL; for (int i = 0; pl_filter_function_presets[i].name; i++) { if (strcmp(pl_filter_function_presets[i].name, name) == 0) return &pl_filter_function_presets[i]; } return NULL; } const struct pl_filter_preset *pl_find_filter_preset(const char *name) { if (!name) return NULL; for (int i = 0; pl_filter_presets[i].name; i++) { if (strcmp(pl_filter_presets[i].name, name) == 0) return &pl_filter_presets[i]; } return NULL; } const struct pl_filter_preset pl_filter_presets[] = { {"none", NULL, "Built-in sampling"}, COMMON_FILTER_PRESETS, {0} }; const int pl_num_filter_presets = PL_ARRAY_SIZE(pl_filter_presets) - 1;