diff options
Diffstat (limited to 'video')
-rw-r--r-- | video/out/filter_kernels.c | 235 | ||||
-rw-r--r-- | video/out/filter_kernels.h | 24 | ||||
-rw-r--r-- | video/out/gl_video.c | 61 | ||||
-rw-r--r-- | video/out/gl_video.h | 2 |
4 files changed, 192 insertions, 130 deletions
diff --git a/video/out/filter_kernels.c b/video/out/filter_kernels.c index 1b8c70ba1a..4f82e80328 100644 --- a/video/out/filter_kernels.c +++ b/video/out/filter_kernels.c @@ -39,13 +39,24 @@ #include "filter_kernels.h" -// NOTE: all filters are separable, symmetric, and are intended for use with -// a lookup table/texture. +// NOTE: all filters are designed for discrete convolution + +const struct filter_window *mp_find_filter_window(const char *name) +{ + if (!name) + return NULL; + + for (const struct filter_window *w = mp_filter_windows; w->name; w++) { + if (strcmp(w->name, name) == 0) + return w; + } + return NULL; +} const struct filter_kernel *mp_find_filter_kernel(const char *name) { - for (const struct filter_kernel *k = mp_filter_kernels; k->name; k++) { - if (strcmp(k->name, name) == 0) + for (const struct filter_kernel *k = mp_filter_kernels; k->f.name; k++) { + if (strcmp(k->f.name, name) == 0) return k; } return NULL; @@ -56,16 +67,22 @@ const struct filter_kernel *mp_find_filter_kernel(const char *name) bool mp_init_filter(struct filter_kernel *filter, const int *sizes, double inv_scale) { - assert(filter->radius > 0); - // polar filters are dependent only on the radius + assert(filter->f.radius > 0); + // Only downscaling requires widening the filter + filter->inv_scale = inv_scale >= 1.0 ? inv_scale : 1.0; + filter->f.radius *= filter->inv_scale; + // Polar filters are dependent solely on the radius if (filter->polar) { + filter->f.radius = fmin(filter->f.radius, 16.0); filter->size = 1; + // Safety precaution to avoid generating a gigantic shader + if (filter->f.radius > 16.0) { + filter->f.radius = 16.0; + return false; + } return true; } - // only downscaling requires widening the filter - filter->inv_scale = inv_scale >= 1.0 ? inv_scale : 1.0; - double support = filter->radius * filter->inv_scale; - int size = ceil(2.0 * support); + int size = ceil(2.0 * filter->f.radius); // round up to smallest available size that's still large enough if (size < sizes[0]) size = sizes[0]; @@ -80,27 +97,42 @@ bool mp_init_filter(struct filter_kernel *filter, const int *sizes, // largest filter available. This is incorrect, but better than refusing // to do anything. filter->size = cursize[-1]; - filter->inv_scale = filter->size / 2.0 / filter->radius; + filter->inv_scale *= (filter->size/2.0) / filter->f.radius; return false; } } +// Sample from the blurred, windowed kernel. Note: The window is always +// stretched to the true radius, regardless of the filter blur/scale. +static double sample_filter(struct filter_kernel *filter, + struct filter_window *window, double x) +{ + double bk = filter->f.blur > 0.0 ? filter->f.blur : 1.0; + double bw = window->blur > 0.0 ? window->blur : 1.0; + double c = fabs(x) / (filter->inv_scale * bk); + double w = window->weight ? window->weight(window, x/bw * window->radius + / filter->f.radius) + : 1.0; + return c < filter->f.radius ? w * filter->f.weight(&filter->f, c) : 0.0; +} + // Calculate the 1D filtering kernel for N sample points. // N = number of samples, which is filter->size // The weights will be stored in out_w[0] to out_w[N - 1] // f = x0 - abs(x0), subpixel position in the range [0,1) or [0,1]. -void mp_compute_weights(struct filter_kernel *filter, double f, float *out_w) +static void mp_compute_weights(struct filter_kernel *filter, + struct filter_window *window, + double f, float *out_w) { assert(filter->size > 0); double sum = 0; for (int n = 0; n < filter->size; n++) { double x = f - (n - filter->size / 2 + 1); - double c = fabs(x) / filter->inv_scale; - double w = c <= filter->radius ? filter->weight(filter, c) : 0; + double w = sample_filter(filter, window, x); out_w[n] = w; sum += w; } - //normalize + // Normalize to preserve energy for (int n = 0; n < filter->size; n++) out_w[n] /= sum; } @@ -109,47 +141,47 @@ void mp_compute_weights(struct filter_kernel *filter, double f, float *out_w) // interpreted as rectangular array of count * filter->size items. void mp_compute_lut(struct filter_kernel *filter, int count, float *out_array) { + struct filter_window *window = &filter->w; if (filter->polar) { // Compute a 1D array indexed by radius - assert(filter->radius > 0); for (int x = 0; x < count; x++) { - double r = x * filter->radius / (count - 1); - out_array[x] = r <= filter->radius ? filter->weight(filter, r) : 0; + double r = x * filter->f.radius / (count - 1); + out_array[x] = sample_filter(filter, window, r); } } else { // Compute a 2D array indexed by subpixel position for (int n = 0; n < count; n++) { - mp_compute_weights(filter, n / (double)(count - 1), + mp_compute_weights(filter, window, n / (double)(count - 1), out_array + filter->size * n); } } } -typedef struct filter_kernel kernel; +typedef struct filter_window params; -static double nearest(kernel *k, double x) +static double box(params *p, double x) { - return x > 0.5 ? 0.0 : 1.0; + // This is mathematically 1.0 everywhere, the clipping is done implicitly + // based on the radius. + return 1.0; } -static double triangle(kernel *k, double x) +static double triangle(params *p, double x) { - if (fabs(x) > 1.0) - return 0.0; - return 1.0 - fabs(x); + return fmax(0.0, 1.0 - fabs(x / p->radius)); } -static double hanning(kernel *k, double x) +static double hanning(params *p, double x) { return 0.5 + 0.5 * cos(M_PI * x); } -static double hamming(kernel *k, double x) +static double hamming(params *p, double x) { return 0.54 + 0.46 * cos(M_PI * x); } -static double quadric(kernel *k, double x) +static double quadric(params *p, double x) { // NOTE: glumpy uses 0.75, AGG uses 0.5 if (x < 0.5) @@ -164,7 +196,7 @@ static double bc_pow3(double x) return (x <= 0) ? 0 : x * x * x; } -static double bicubic(kernel *k, double x) +static double bicubic(params *p, double x) { return (1.0/6.0) * ( bc_pow3(x + 2) - 4 * bc_pow3(x + 1) @@ -184,19 +216,19 @@ static double bessel_i0(double epsilon, double x) return sum; } -static double kaiser(kernel *k, double x) +static double kaiser(params *p, double x) { - double a = k->params[0]; + double a = p->params[0]; double epsilon = 1e-12; double i0a = 1 / bessel_i0(epsilon, a); return bessel_i0(epsilon, a * sqrt(1 - x * x)) * i0a; } // Family of cubic B/C splines -static double cubic_bc(kernel *k, double x) +static double cubic_bc(params *p, double x) { - double b = k->params[0]; - double c = k->params[1]; + double b = p->params[0]; + double c = p->params[1]; double p0 = (6.0 - 2.0 * b) / 6.0, p2 = (-18.0 + 12.0 * b + 6.0 * c) / 6.0, @@ -212,14 +244,14 @@ static double cubic_bc(kernel *k, double x) return 0; } -static double spline16(kernel *k, double x) +static double spline16(params *p, double x) { if (x < 1.0) return ((x - 9.0/5.0 ) * x - 1.0/5.0 ) * x + 1.0; return ((-1.0/3.0 * (x-1) + 4.0/5.0) * (x-1) - 7.0/15.0 ) * (x-1); } -static double spline36(kernel *k, double x) +static double spline36(params *p, double x) { if(x < 1.0) return ((13.0/11.0 * x - 453.0/209.0) * x - 3.0/209.0) * x + 1.0; @@ -230,7 +262,7 @@ static double spline36(kernel *k, double x) * (x - 2); } -static double spline64(kernel *k, double x) +static double spline64(params *p, double x) { if (x < 1.0) return ((49.0 / 41.0 * x - 6387.0 / 2911.0) * x - 3.0 / 2911.0) * x + 1.0; @@ -244,107 +276,84 @@ static double spline64(kernel *k, double x) * (x - 3); } -static double gaussian(kernel *k, double x) +static double gaussian(params *p, double x) { - double p = k->params[0]; - return pow(2.0, -(M_E / p) * x * x); + return pow(2.0, -(M_E / p->params[0]) * x * x); } -static double sinc(kernel *k, double x) +static double sinc(params *p, double x) { - if (x == 0.0) + if (fabs(x) < 1e-8) return 1.0; double pix = M_PI * x; return sin(pix) / pix; } -static double jinc(kernel *k, double x) +static double jinc(params *p, double x) { if (fabs(x) < 1e-8) return 1.0; - double pix = M_PI * x / k->params[0]; // blur factor - return 2.0 * j1(pix) / pix; -} - -static double lanczos(kernel *k, double x) -{ - double radius = k->size / 2; - if (x < -radius || x > radius) - return 0; - if (x == 0) - return 1; double pix = M_PI * x; - return radius * sin(pix) * sin(pix / radius) / (pix * pix); -} - -static double ewa_ginseng(kernel *k, double x) -{ - // Note: This is EWA ginseng, aka sinc-windowed jinc. - // Not to be confused with tensor ginseng, aka jinc-windowed sinc. - double radius = k->radius; - if (fabs(x) >= radius) - return 0.0; - return jinc(k, x) * sinc(k, x / radius); -} - -static double ewa_lanczos(kernel *k, double x) -{ - double radius = k->radius; - if (fabs(x) >= radius) - return 0.0; - // First zero of the jinc function. We simply scale it to fit into the - // given radius. - double jinc_zero = 1.2196698912665045; - return jinc(k, x) * jinc(k, x * jinc_zero / radius); -} - -static double ewa_hanning(kernel *k, double x) -{ - double radius = k->radius; - if (fabs(x) >= radius) - return 0.0; - // Jinc windowed by the hanning window - return jinc(k, x) * hanning(k, x / radius); + return 2.0 * j1(pix) / pix; } -static double blackman(kernel *k, double x) +static double sphinx(params *p, double x) { - double radius = k->size / 2; - if (x == 0.0) + if (fabs(x) < 1e-8) return 1.0; - if (x > radius) - return 0.0; - x *= M_PI; - double xr = x / radius; - return (sin(x) / x) * (0.42 + 0.5 * cos(xr) + 0.08 * cos(2 * xr)); + double pix = M_PI * x; + return 3.0 * (sin(pix) - pix * cos(pix)) / (pix * pix * pix); } -const struct filter_kernel mp_filter_kernels[] = { - {"nearest", 0.5, nearest}, +const struct filter_window mp_filter_windows[] = { + {"box", 1, box}, {"triangle", 1, triangle}, {"hanning", 1, hanning}, {"hamming", 1, hamming}, {"quadric", 1.5, quadric}, - {"bicubic", 2, bicubic}, {"kaiser", 1, kaiser, .params = {6.33, NAN} }, - {"catmull_rom", 2, cubic_bc, .params = {0.0, 0.5} }, - {"mitchell", 2, cubic_bc, .params = {1.0/3.0, 1.0/3.0} }, {"hermite", 1, cubic_bc, .params = {0.0, 0.0} }, - {"robidoux", 2, cubic_bc, .params = {0.3782, 0.3109}, .polar = true}, - {"robidouxsharp", 2, cubic_bc, .params = {0.2620, 0.3690}, .polar = true}, - {"spline16", 2, spline16}, - {"spline36", 3, spline36}, - {"spline64", 4, spline64}, {"gaussian", -1, gaussian, .params = {1.0, NAN} }, - {"sinc", -1, sinc}, - {"ewa_lanczos", -1, ewa_lanczos, .params = {1.0, NAN}, .polar = true}, - {"ewa_hanning", -1, ewa_hanning, .params = {1.0, NAN}, .polar = true}, - {"ewa_ginseng", -1, ewa_ginseng, .params = {1.0, NAN}, .polar = true}, + {"sinc", 1, sinc}, + {"jinc", 1.2196698912665045, jinc}, + {"sphinx", 1.4302966531242027, sphinx}, + {0} +}; + +const struct filter_kernel mp_filter_kernels[] = { + // Spline filters + {{"spline16", 2, spline16}}, + {{"spline36", 3, spline36}}, + {{"spline64", 4, spline64}}, + // Sinc filters + {{"sinc", -1, sinc}}, + {{"lanczos", -1, sinc}, .window = "sinc"}, + {{"ginseng", -1, sinc}, .window = "jinc"}, + // Jinc filters + {{"jinc", -1, jinc}, .polar = true}, + {{"ewa_lanczos", -1, jinc}, .polar = true, .window = "jinc"}, + {{"ewa_hanning", -1, jinc}, .polar = true, .window = "hanning" }, + {{"ewa_ginseng", -1, jinc}, .polar = true, .window = "sinc"}, // Radius is based on the true jinc radius, slightly sharpened as per // calculations by Nicolas Robidoux. Source: Imagemagick's magick/resize.c - {"ewa_lanczossharp", 3.2383154841662362, ewa_lanczos, - .params = {0.9812505644269356, NAN}, .polar = true}, - {"lanczos", -1, lanczos}, - {"blackman", -1, blackman}, - {0} + {{"ewa_lanczossharp", 3.2383154841662362, jinc, .blur = 0.9812505644269356}, + .polar = true, .window = "jinc"}, + // Similar to the above, but softened instead. This one makes hash patterns + // disappear completely. Blur determined by trial and error. + {{"ewa_lanczossoft", 3.2383154841662362, jinc, .blur = 1.015}, + .polar = true, .window = "jinc"}, + // Cubic filters + {{"bicubic", 2, bicubic}}, + {{"bcspline", 2, cubic_bc, .params = {0.5, 0.5} }}, + {{"catmull_rom", 2, cubic_bc, .params = {0.0, 0.5} }}, + {{"mitchell", 2, cubic_bc, .params = {1.0/3.0, 1.0/3.0} }}, + {{"robidoux", 2, cubic_bc, .params = {0.3782, 0.3109}}, .polar = true}, + {{"robidouxsharp", 2, cubic_bc, .params = {0.2620, 0.3690}}, .polar = true}, + // Miscalleaneous filters + {{"box", -1, box}}, + {{"nearest", 0.5, box}}, + {{"triangle", -1, triangle}}, + {{"bilinear_slow", 1, triangle}}, + {{"gaussian", -1, gaussian, .params = {1.0, NAN} }}, + {{0}} }; diff --git a/video/out/filter_kernels.h b/video/out/filter_kernels.h index b2e07863fd..99776d2f07 100644 --- a/video/out/filter_kernels.h +++ b/video/out/filter_kernels.h @@ -21,26 +21,34 @@ #include <stdbool.h> -struct filter_kernel { +struct filter_window { const char *name; - double radius; // A negative value will use user specified radius instead. - double (*weight)(struct filter_kernel *kernel, double x); + double radius; // A negative value will use user specified radius instead. + double (*weight)(struct filter_window *k, double x); + double params[2]; // User-defined custom filter parameters. Not used by + // all filters + double blur; // Blur coefficient (sharpens or widens the filter) +}; - // The filter params can be changed at runtime. Only used by some filters. - float params[2]; - // Whether or not the filter uses polar coordinates - bool polar; +struct filter_kernel { + struct filter_window f; // the kernel itself + struct filter_window w; // window storage + // Constant values + const char *window; // default window + bool polar; // whether or not the filter uses polar coordinates // The following values are set by mp_init_filter() at runtime. int size; // number of coefficients (may depend on radius) double inv_scale; // scale factor (<1.0 is upscale, >1.0 downscale) }; +extern const struct filter_window mp_filter_windows[]; extern const struct filter_kernel mp_filter_kernels[]; +const struct filter_window *mp_find_filter_window(const char *name); const struct filter_kernel *mp_find_filter_kernel(const char *name); + bool mp_init_filter(struct filter_kernel *filter, const int *sizes, double scale); -void mp_compute_weights(struct filter_kernel *filter, double f, float *out_w); void mp_compute_lut(struct filter_kernel *filter, int count, float *out_array); #endif /* MPLAYER_FILTER_KERNELS_H */ diff --git a/video/out/gl_video.c b/video/out/gl_video.c index 94b2e93564..7f426bfd1f 100644 --- a/video/out/gl_video.c +++ b/video/out/gl_video.c @@ -357,6 +357,9 @@ const struct gl_video_opts gl_video_opts_hq_def = { static int validate_scaler_opt(struct mp_log *log, const m_option_t *opt, struct bstr name, struct bstr param); +static int validate_window_opt(struct mp_log *log, const m_option_t *opt, + struct bstr name, struct bstr param); + #define OPT_BASE_STRUCT struct gl_video_opts const struct m_sub_options gl_video_conf = { .opts = (const m_option_t[]) { @@ -387,6 +390,12 @@ const struct m_sub_options gl_video_conf = { OPT_FLOAT("cscale-param2", scaler_params[1][1], 0), OPT_FLOAT("tscale-param1", scaler_params[2][0], 0), OPT_FLOAT("tscale-param2", scaler_params[2][1], 0), + OPT_FLOAT("scale-blur", scaler_blur[0], 0), + OPT_FLOAT("cscale-blur", scaler_blur[1], 0), + OPT_FLOAT("tscale-blur", scaler_blur[2], 0), + OPT_STRING_VALIDATE("scale-window", scaler_window[0], 0, validate_window_opt), + OPT_STRING_VALIDATE("cscale-window", scaler_window[1], 0, validate_window_opt), + OPT_STRING_VALIDATE("tscale-window", scaler_window[2], 0, validate_window_opt), OPT_FLOATRANGE("scale-radius", scaler_radius[0], 0, 1.0, 16.0), OPT_FLOATRANGE("cscale-radius", scaler_radius[1], 0, 1.0, 16.0), OPT_FLOATRANGE("tscale-radius", scaler_radius[2], 0, 1.0, 3.0), @@ -893,6 +902,7 @@ static void reinit_scaler(struct gl_video *p, int scaler_unit, const char *name, for (int n = 0; n < 2; n++) scaler->params[n] = p->opts.scaler_params[scaler->index][n]; + scaler->antiring = p->opts.scaler_antiring[scaler->index]; const struct filter_kernel *t_kernel = mp_find_filter_kernel(scaler->name); if (!t_kernel) @@ -901,15 +911,24 @@ static void reinit_scaler(struct gl_video *p, int scaler_unit, const char *name, scaler->kernel_storage = *t_kernel; scaler->kernel = &scaler->kernel_storage; + const char *win = p->opts.scaler_window[scaler->index]; + if (!win || !win[0]) + win = t_kernel->window; + const struct filter_window *t_window = mp_find_filter_window(win); + if (t_window) + scaler->kernel->w = *t_window; + for (int n = 0; n < 2; n++) { if (!isnan(scaler->params[n])) - scaler->kernel->params[n] = scaler->params[n]; + scaler->kernel->f.params[n] = scaler->params[n]; } - scaler->antiring = p->opts.scaler_antiring[scaler->index]; + float blur = p->opts.scaler_blur[scaler->index]; + if (blur > 0.0) + scaler->kernel->f.blur = blur; - if (scaler->kernel->radius < 0) - scaler->kernel->radius = p->opts.scaler_radius[scaler->index]; + if (scaler->kernel->f.radius < 0) + scaler->kernel->f.radius = p->opts.scaler_radius[scaler->index]; scaler->insufficient = !mp_init_filter(scaler->kernel, sizes, scale_factor); @@ -1064,7 +1083,7 @@ static void pass_sample_separated(struct gl_video *p, int src_tex, static void pass_sample_polar(struct gl_video *p, struct scaler *scaler) { - double radius = scaler->kernel->radius; + double radius = scaler->kernel->f.radius; int bound = (int)ceil(radius); bool use_ar = scaler->antiring > 0; GLSL(vec4 color = vec4(0.0);) @@ -2491,7 +2510,7 @@ static const char *handle_scaler_opt(const char *name, bool tscale) if (name && name[0]) { const struct filter_kernel *kernel = mp_find_filter_kernel(name); if (kernel && (!tscale || !kernel->polar)) - return kernel->name; + return kernel->f.name; for (const char *const *filter = tscale ? fixed_tscale_filters : fixed_scale_filters; @@ -2520,7 +2539,7 @@ void gl_video_set_options(struct gl_video *p, struct gl_video_opts *opts, if (queue_size && p->opts.interpolation && p->opts.scalers[2]) { const struct filter_kernel *kernel = mp_find_filter_kernel(p->opts.scalers[2]); if (kernel) { - double radius = kernel->radius; + double radius = kernel->f.radius; radius = radius > 0 ? radius : p->opts.scaler_radius[2]; *queue_size = 50e3 * ceil(radius); } @@ -2566,9 +2585,9 @@ static int validate_scaler_opt(struct mp_log *log, const m_option_t *opt, *filter; filter++) { mp_info(log, " %s\n", *filter); } - for (int n = 0; mp_filter_kernels[n].name; n++) { + for (int n = 0; mp_filter_kernels[n].f.name; n++) { if (!tscale || !mp_filter_kernels[n].polar) - mp_info(log, " %s\n", mp_filter_kernels[n].name); + mp_info(log, " %s\n", mp_filter_kernels[n].f.name); } if (s[0]) mp_fatal(log, "No scaler named '%s' found!\n", s); @@ -2576,6 +2595,30 @@ static int validate_scaler_opt(struct mp_log *log, const m_option_t *opt, return r; } +static int validate_window_opt(struct mp_log *log, const m_option_t *opt, + struct bstr name, struct bstr param) +{ + char s[20] = {0}; + int r = 1; + if (bstr_equals0(param, "help")) { + r = M_OPT_EXIT - 1; + } else { + snprintf(s, sizeof(s), "%.*s", BSTR_P(param)); + const struct filter_window *window = mp_find_filter_window(s); + if (!window) + r = M_OPT_INVALID; + } + if (r < 1) { + mp_info(log, "Available windows:\n"); + for (int n = 0; mp_filter_windows[n].name; n++) + mp_info(log, " %s\n", mp_filter_windows[n].name); + if (s[0]) + mp_fatal(log, "No window named '%s' found!\n", s); + } + return r; +} + + // Resize and redraw the contents of the window without further configuration. // Intended to be used in situations where the frontend can't really be // involved with reconfiguring the VO properly. diff --git a/video/out/gl_video.h b/video/out/gl_video.h index 2f1203c40c..f3267d69c4 100644 --- a/video/out/gl_video.h +++ b/video/out/gl_video.h @@ -36,8 +36,10 @@ struct gl_video_opts { int target_prim; int target_trc; float scaler_params[3][2]; + float scaler_blur[3]; float scaler_radius[3]; float scaler_antiring[3]; + char *scaler_window[3]; int linear_scaling; int fancy_downscaling; int sigmoid_upscaling; |