summaryrefslogtreecommitdiffstats
path: root/video
diff options
context:
space:
mode:
Diffstat (limited to 'video')
-rw-r--r--video/out/filter_kernels.c235
-rw-r--r--video/out/filter_kernels.h24
-rw-r--r--video/out/gl_video.c61
-rw-r--r--video/out/gl_video.h2
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;