From 2d8936f0e4c16df8cd17a600f5185be1634ed7e7 Mon Sep 17 00:00:00 2001 From: Frechdachs Date: Sat, 2 Apr 2022 10:44:00 +0200 Subject: [PATCH] Add custom kernels Functionality is about the same as in Akarin's fork, but it's implemented in a different way internally. Also added force/force_h/force_v paramenters to force sampling even if the resolution stays the same. Only supported in the VapourSynth plugin. --- README.md | 29 ++++++--- descale.py | 6 +- include/descale.h | 13 +++- meson.build | 15 +++-- src/avsplugin.c | 10 ++- src/descale.c | 19 +++--- src/vsplugin.c | 157 +++++++++++++++++++++++++++++++++++++++------- 7 files changed, 202 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 023a1a0..9e5fc31 100644 --- a/README.md +++ b/README.md @@ -10,22 +10,37 @@ The VapourSynth plugin itself supports every constant input format. If the forma The included python wrapper, contrary to using the plugin directly, doesn't descale the chroma planes but scales them normally with `Spline36`. ``` -descale.Debilinear(clip src, int width, int height, float src_left=0.0, float src_top=0.0, float src_width=width, float src_height=height, int opt=0) +descale.Debilinear(clip src, int width, int height, float src_left=0.0, float src_top=0.0, float src_width=width, float src_height=height, bool force, bool force_h, bool force_v, int opt=0) -descale.Debicubic(clip src, int width, int height, float b=0.0, float c=0.5, float src_left=0.0, float src_top=0.0, float src_width=width, float src_height=height, int opt=0) +descale.Debicubic(clip src, int width, int height, float b=0.0, float c=0.5, float src_left=0.0, float src_top=0.0, float src_width=width, float src_height=height, bool force, bool force_h, bool force_v, int opt=0) -descale.Delanczos(clip src, int width, int height, int taps=3, float src_left=0.0, float src_top=0.0, float src_width=width, float src_height=height, int opt=0) +descale.Delanczos(clip src, int width, int height, int taps=3, float src_left=0.0, float src_top=0.0, float src_width=width, float src_height=height, bool force, bool force_h, bool force_v, int opt=0) -descale.Despline16(clip src, int width, int height, float src_left=0.0, float src_top=0.0, float src_width=width, float src_height=height, int opt=0) +descale.Despline16(clip src, int width, int height, float src_left=0.0, float src_top=0.0, float src_width=width, float src_height=height, bool force, bool force_h, bool force_v, int opt=0) -descale.Despline36(clip src, int width, int height, float src_left=0.0, float src_top=0.0, float src_width=width, float src_height=height, int opt=0) +descale.Despline36(clip src, int width, int height, float src_left=0.0, float src_top=0.0, float src_width=width, float src_height=height, bool force, bool force_h, bool force_v, int opt=0) -descale.Despline64(clip src, int width, int height, float src_left=0.0, float src_top=0.0, float src_width=width, float src_height=height, int opt=0) +descale.Despline64(clip src, int width, int height, float src_left=0.0, float src_top=0.0, float src_width=width, float src_height=height, bool force, bool force_h, bool force_v, int opt=0) -descale.Descale(clip src, int width, int height, str kernel, int taps=3, float b=0.0, float c=0.0, float src_left=0.0, float src_top=0.0, float src_width=width, float src_height=height, int opt=0) +descale.Descale(clip src, int width, int height, str kernel, func custom_kernel, int taps=3, float b=0.0, float c=0.0, float src_left=0.0, float src_top=0.0, float src_width=width, float src_height=height, bool force, bool force_h, bool force_v, int opt=0) ``` The AviSynth+ plugin is used similarly, but without the `descale` namespace. +Custom kernels are only supported in the VapourSynth plugin. + +### Custom kernels + +```python +# Debilinear +core.descale.Descale(src, w, h, custom_kernel=lambda x: 1.0 - x, taps=1) + +# Delanczos +import math +def sinc(x): + return 1.0 if x == 0 else math.sin(x * math.pi) / (x * math.pi) +taps = 3 +core.descale.Descale(src, w, h, custom_kernel=lambda x: sinc(x) * sinc(x / taps), taps=taps) +``` ## How does this work? diff --git a/descale.py b/descale.py index 8e28647..95891d3 100644 --- a/descale.py +++ b/descale.py @@ -22,7 +22,7 @@ def Despline64(src, width, height, yuv444=False, gray=False, chromaloc=None): return Descale(src, width, height, kernel='spline64', taps=None, b=None, c=None, yuv444=yuv444, gray=gray, chromaloc=chromaloc) -def Descale(src, width, height, kernel='bilinear', taps=3, b=0.0, c=0.5, yuv444=False, gray=False, chromaloc=None): +def Descale(src, width, height, kernel=None, custom_kernel=None, taps=None, b=None, c=None, yuv444=False, gray=False, chromaloc=None): src_f = src.format src_cf = src_f.color_family src_st = src_f.sample_type @@ -31,10 +31,10 @@ def Descale(src, width, height, kernel='bilinear', taps=3, b=0.0, c=0.5, yuv444= src_sh = src_f.subsampling_h if src_cf == RGB and not gray: - rgb = to_rgbs(src).descale.Descale(width, height, kernel, taps, b, c) + rgb = to_rgbs(src).descale.Descale(width, height, kernel, custom_kernel, taps, b, c) return rgb.resize.Point(format=src_f.id) - y = to_grays(src).descale.Descale(width, height, kernel, taps, b, c) + y = to_grays(src).descale.Descale(width, height, kernel, custom_kernel, taps, b, c) y_f = core.register_format(GRAY, src_st, src_bits, 0, 0) y = y.resize.Point(format=y_f.id) diff --git a/include/descale.h b/include/descale.h index 552705b..7eccc9a 100644 --- a/include/descale.h +++ b/include/descale.h @@ -32,7 +32,8 @@ typedef enum DescaleMode DESCALE_MODE_LANCZOS = 3, DESCALE_MODE_SPLINE16 = 4, DESCALE_MODE_SPLINE36 = 5, - DESCALE_MODE_SPLINE64 = 6 + DESCALE_MODE_SPLINE64 = 6, + DESCALE_MODE_CUSTOM = 7 } DescaleMode; @@ -51,15 +52,23 @@ typedef enum DescaleOpt } DescaleOpt; +typedef struct DescaleCustomKernel +{ + double (*f)(double x, void *user_data); + void *user_data; +} DescaleCustomKernel; + + // Optional struct members should be initialized to 0 if not used typedef struct DescaleParams { enum DescaleMode mode; - int taps; // required if mode is LANCZOS + int taps; // required if mode is LANCZOS or CUSTOM double param1; // required if mode is BICUBIC double param2; // required if mode is BICUBIC double shift; // optional double active_dim; // always required; usually equal to dst_dim + DescaleCustomKernel custom_kernel; // required if mode is CUSTOM } DescaleParams; diff --git a/meson.build b/meson.build index fecce53..a132ce7 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project('Descale', 'c', default_options: ['buildtype=release', 'b_ndebug=if-release', 'c_std=c99'], meson_version: '>=0.51.0', - version: '7' + version: '8' ) add_global_arguments(['-D_XOPEN_SOURCE=700'], language: 'c') @@ -47,12 +47,19 @@ if host_machine.system() == 'windows' else m_dep = cc.find_library('m', required: false) p_dep = cc.find_library('pthread') - vs = dependency('vapoursynth').partial_dependency(compile_args: true, includes: true) - deps += [m_dep, p_dep, vs] + deps += [m_dep, p_dep] + if libtype in ['vapoursynth', 'both'] + vs = dependency('vapoursynth').partial_dependency(compile_args: true, includes: true) + deps += [vs] + endif + if libtype in ['avisynth', 'both'] + avs = dependency('avisynth') + deps += [avs] + endif if libtype in ['vapoursynth', 'both'] installdir = join_paths(vs.get_pkgconfig_variable('libdir'), 'vapoursynth') else - installdir = join_paths(vs.get_pkgconfig_variable('libdir'), 'avisynth') + installdir = join_paths(avs.get_pkgconfig_variable('libdir'), 'avisynth') endif endif diff --git a/src/avsplugin.c b/src/avsplugin.c index 72cceac..d4ac9b9 100644 --- a/src/avsplugin.c +++ b/src/avsplugin.c @@ -245,8 +245,7 @@ static AVS_Value AVSC_CC avs_descale_create(AVS_ScriptEnvironment *env, AVS_Valu b = avs_defined(v) ? avs_as_float(v) : 0.0; v = avs_array_elt(args, idx++); c = avs_defined(v) ? avs_as_float(v) : 0.5; - } - else { + } else { b = 0.0; c = 0.5; } @@ -263,6 +262,11 @@ static AVS_Value AVSC_CC avs_descale_create(AVS_ScriptEnvironment *env, AVS_Valu bool process_h = dst_width != src_width || shift_h != 0.0 || active_width != (double)dst_width; bool process_v = dst_height != src_height || shift_v != 0.0 || active_height != (double)dst_height; + if (!process_h && !process_v) { + v = avs_new_value_clip(clip); + goto done; + } + v = avs_array_elt(args, idx++); int opt = avs_defined(v) ? avs_as_int(v) : 0; enum DescaleOpt opt_enum; @@ -277,7 +281,7 @@ static AVS_Value AVSC_CC avs_descale_create(AVS_ScriptEnvironment *env, AVS_Valu if (bytes_per_pixel != 4) { AVS_Value c2, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11; c1 = avs_new_value_clip(clip); - //avs_release_clip(clip); // This is apparently wrong, leaving it uncommented crashes sometimes + avs_release_clip(clip); a1 = avs_new_value_int(32); AVS_Value convert_args[] = {c1, a1}; c2 = avs_invoke(env, "ConvertBits", avs_new_value_array(convert_args, 2), NULL); diff --git a/src/descale.c b/src/descale.c index 0df055c..3224908 100644 --- a/src/descale.c +++ b/src/descale.c @@ -163,7 +163,7 @@ static inline double cube(double x) } -static double calculate_weight(enum DescaleMode mode, int support, double distance, double b, double c) +static double calculate_weight(enum DescaleMode mode, int support, double distance, double b, double c, struct DescaleCustomKernel *ck) { distance = fabs(distance); @@ -221,6 +221,8 @@ static double calculate_weight(enum DescaleMode mode, int support, double distan } else { return 0.0; } + } else if (mode == DESCALE_MODE_CUSTOM) { + return ck->f(distance, ck->user_data); } return 0.0; @@ -242,7 +244,7 @@ static double round_halfup(double x) // Most of this is taken from zimg // https://github.com/sekrit-twc/zimg/blob/ce27c27f2147fbb28e417fbf19a95d3cf5d68f4f/src/zimg/resize/filter.cpp#L227 -static void scaling_weights(enum DescaleMode mode, int support, int src_dim, int dst_dim, double param1, double param2, double shift, double active_dim, double **weights) +static void scaling_weights(enum DescaleMode mode, int support, int src_dim, int dst_dim, double param1, double param2, double shift, double active_dim, struct DescaleCustomKernel *ck, double **weights) { *weights = calloc(src_dim * dst_dim, sizeof (double)); double ratio = (double)dst_dim / active_dim; @@ -254,7 +256,7 @@ static void scaling_weights(enum DescaleMode mode, int support, int src_dim, int double begin_pos = round_halfup(pos - support) + 0.5; for (int j = 0; j < 2 * support; j++) { double xpos = begin_pos + j; - total += calculate_weight(mode, support, xpos - pos, param1, param2); + total += calculate_weight(mode, support, xpos - pos, param1, param2, ck); } for (int j = 0; j < 2 * support; j++) { double xpos = begin_pos + j; @@ -269,7 +271,7 @@ static void scaling_weights(enum DescaleMode mode, int support, int src_dim, int real_pos = xpos; int idx = (int)floor(real_pos); - (*weights)[i * src_dim + idx] += calculate_weight(mode, support, xpos - pos, param1, param2) / total; + (*weights)[i * src_dim + idx] += calculate_weight(mode, support, xpos - pos, param1, param2, ck) / total; } } } @@ -561,18 +563,21 @@ static struct DescaleCore *create_core(int src_dim, int dst_dim, struct DescaleP support = 2; } else if (params->mode == DESCALE_MODE_LANCZOS) { support = params->taps; - if (support == 0) - return NULL; } else if (params->mode == DESCALE_MODE_SPLINE16) { support = 2; } else if (params->mode == DESCALE_MODE_SPLINE36) { support = 3; } else if (params->mode == DESCALE_MODE_SPLINE64) { support = 4; + } else if (params->mode == DESCALE_MODE_CUSTOM) { + support = params->taps; } else { return NULL; } + if (support == 0) + return NULL; + core.src_dim = src_dim; core.dst_dim = dst_dim; core.bandwidth = support * 4 - 1; @@ -582,7 +587,7 @@ static struct DescaleCore *create_core(int src_dim, int dst_dim, struct DescaleP double *multiplied_weights; double *lower; - scaling_weights(params->mode, support, dst_dim, src_dim, params->param1, params->param2, params->shift, params->active_dim, &weights); + scaling_weights(params->mode, support, dst_dim, src_dim, params->param1, params->param2, params->shift, params->active_dim, ¶ms->custom_kernel, &weights); transpose_matrix(src_dim, dst_dim, weights, &transposed_weights); core.weights_left_idx = calloc(ceil_n(dst_dim, 8), sizeof (int)); diff --git a/src/vsplugin.c b/src/vsplugin.c index 6d4ce2b..8ca466f 100644 --- a/src/vsplugin.c +++ b/src/vsplugin.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,13 @@ struct VSDescaleData }; +struct VSCustomKernelData +{ + const VSAPI *vsapi; + VSFunction *custom_kernel; +}; + + static const VSFrame *VS_CC descale_get_frame(int n, int activation_reason, void *instance_data, void **frame_data, VSFrameContext *frame_ctx, VSCore *core, const VSAPI *vsapi) { struct VSDescaleData *d = (struct VSDescaleData *)instance_data; @@ -119,33 +127,90 @@ static void VS_CC descale_free(void *instance_data, VSCore *core, const VSAPI *v pthread_mutex_destroy(&d->lock); + if (d->dd.params.mode == DESCALE_MODE_CUSTOM) { + struct VSCustomKernelData *kd = (struct VSCustomKernelData *)d->dd.params.custom_kernel.user_data; + vsapi->freeFunction(kd->custom_kernel); + free(kd); + } + free(d); } +static double custom_kernel_f(double x, void *user_data) +{ + struct VSCustomKernelData *kd = (struct VSCustomKernelData *)user_data; + + VSMap *in = kd->vsapi->createMap(); + VSMap *out = kd->vsapi->createMap(); + kd->vsapi->mapSetFloat(in, "x", x, maReplace); + kd->vsapi->callFunction(kd->custom_kernel, in, out); + if (kd->vsapi->mapGetError(out)) { + fprintf(stderr, "Descale: custom kernel error: %s.\n", kd->vsapi->mapGetError(out)); + kd->vsapi->freeMap(in); + kd->vsapi->freeMap(out); + return 0.0; + } + int err; + x = kd->vsapi->mapGetFloat(out, "val", 0, &err); + if (err) + x = (double)kd->vsapi->mapGetInt(out, "val", 0, &err); + if (err) { + fprintf(stderr, "Descale: custom kernel: The custom kernel function returned a value that is neither float nor int."); + x = 0.0; + } + kd->vsapi->freeMap(in); + kd->vsapi->freeMap(out); + + return x; +} + + static void VS_CC descale_create(const VSMap *in, VSMap *out, void *user_data, VSCore *core, const VSAPI *vsapi) { struct VSDescaleData d = {0}; struct DescaleParams params = {0}; + VSFunction *custom_kernel = NULL; if (user_data == NULL) { - const char *kernel = vsapi->mapGetData(in, "kernel", 0, NULL); - if (string_is_equal_ignore_case(kernel, "bilinear")) - params.mode = DESCALE_MODE_BILINEAR; - else if (string_is_equal_ignore_case(kernel, "bicubic")) - params.mode = DESCALE_MODE_BICUBIC; - else if (string_is_equal_ignore_case(kernel, "lanczos")) - params.mode = DESCALE_MODE_LANCZOS; - else if (string_is_equal_ignore_case(kernel, "spline16")) - params.mode = DESCALE_MODE_SPLINE16; - else if (string_is_equal_ignore_case(kernel, "spline36")) - params.mode = DESCALE_MODE_SPLINE36; - else if (string_is_equal_ignore_case(kernel, "spline64")) - params.mode = DESCALE_MODE_SPLINE64; - else { - vsapi->mapSetError(out, "Descale: Invalid kernel specified."); + int no_kernel; + int no_custom_kernel; + const char *kernel = vsapi->mapGetData(in, "kernel", 0, &no_kernel); + custom_kernel = vsapi->mapGetFunction(in, "custom_kernel", 0, &no_custom_kernel); + if (!no_kernel && !no_custom_kernel) { + vsapi->mapSetError(out, "Descale: Specify either kernel or custom_kernel, but not both."); + vsapi->freeFunction(custom_kernel); return; } + if (no_kernel && no_custom_kernel) { + vsapi->mapSetError(out, "Descale: Either kernel or custom_kernel must be specified."); + return; + } + if (!no_kernel) { + if (string_is_equal_ignore_case(kernel, "bilinear")) + params.mode = DESCALE_MODE_BILINEAR; + else if (string_is_equal_ignore_case(kernel, "bicubic")) + params.mode = DESCALE_MODE_BICUBIC; + else if (string_is_equal_ignore_case(kernel, "lanczos")) + params.mode = DESCALE_MODE_LANCZOS; + else if (string_is_equal_ignore_case(kernel, "spline16")) + params.mode = DESCALE_MODE_SPLINE16; + else if (string_is_equal_ignore_case(kernel, "spline36")) + params.mode = DESCALE_MODE_SPLINE36; + else if (string_is_equal_ignore_case(kernel, "spline64")) + params.mode = DESCALE_MODE_SPLINE64; + else { + vsapi->mapSetError(out, "Descale: Invalid kernel specified."); + return; + } + } else { + params.mode = DESCALE_MODE_CUSTOM; + params.custom_kernel.f = &custom_kernel_f; + struct VSCustomKernelData *kd = malloc(sizeof (struct VSCustomKernelData)); + kd->vsapi = vsapi; + kd->custom_kernel = custom_kernel; + params.custom_kernel.user_data = kd; + } } else { params.mode = (enum DescaleMode)user_data; } @@ -247,15 +312,26 @@ static void VS_CC descale_create(const VSMap *in, VSMap *out, void *user_data, V funcname = "Debicubic"; // If b != 0 Bicubic is not an interpolation filter, so force processing - if (params.param1 != 0) { + /*if (params.param1 != 0) { d.dd.process_h = true; d.dd.process_v = true; - } + }*/ + // Leaving this check in would make it impossible to only descale a single dimension if this precondition is met. + // If you want to force sampling use the force/force_h/force_v paramenter of the generic Descale filter. - } else if (params.mode == DESCALE_MODE_LANCZOS) { + } else if (params.mode == DESCALE_MODE_LANCZOS || params.mode == DESCALE_MODE_CUSTOM) { params.taps = vsapi->mapGetIntSaturated(in, "taps", 0, &err); - if (err) + + if (err && params.mode == DESCALE_MODE_CUSTOM) { + vsapi->mapSetError(out, "Descale: If custom_kernel is specified, then taps must also be specified."); + vsapi->freeFunction(custom_kernel); + free(params.custom_kernel.user_data); + vsapi->freeNode(d.node); + return; + + } else if (err) { params.taps = 3; + } if (params.taps < 1) { vsapi->mapSetError(out, "Descale: taps must be greater than 0."); @@ -277,6 +353,17 @@ static void VS_CC descale_create(const VSMap *in, VSMap *out, void *user_data, V funcname = "none"; } + int force = vsapi->mapGetIntSaturated(in, "force", 0, &err); + int force_h = vsapi->mapGetIntSaturated(in, "force_h", 0, &err); + if (err) + force_h = force; + int force_v = vsapi->mapGetIntSaturated(in, "force_v", 0, &err); + if (err) + force_v = force; + + d.dd.process_h = d.dd.process_h || force_h; + d.dd.process_v = d.dd.process_v || force_v; + // Return the input clip if no processing is necessary if (!d.dd.process_h && !d.dd.process_v) { vsapi->mapSetNode(out, "clip", d.node, maReplace); @@ -318,7 +405,13 @@ static void VS_CC descale_create(const VSMap *in, VSMap *out, void *user_data, V vsapi->mapSetNode(map1, "src", tmp_node, maReplace); vsapi->mapSetInt(map1, "width", d.dd.dst_width, maReplace); vsapi->mapSetInt(map1, "height", d.dd.dst_height, maReplace); - vsapi->mapSetData(map1, "kernel", funcname + 2, -1, dtUtf8, maReplace); + if (params.mode == DESCALE_MODE_CUSTOM) { + vsapi->mapSetFunction(map1, "custom_kernel", custom_kernel, maReplace); + vsapi->freeFunction(custom_kernel); + free(params.custom_kernel.user_data); + } else { + vsapi->mapSetData(map1, "kernel", funcname + 2, -1, dtUtf8, maReplace); + } vsapi->mapSetInt(map1, "taps", params.taps, maReplace); vsapi->mapSetFloat(map1, "b", params.param1, maReplace); vsapi->mapSetFloat(map1, "c", params.param2, maReplace); @@ -326,6 +419,9 @@ static void VS_CC descale_create(const VSMap *in, VSMap *out, void *user_data, V vsapi->mapSetFloat(map1, "src_top", d.dd.shift_v, maReplace); vsapi->mapSetFloat(map1, "src_width", d.dd.active_width, maReplace); vsapi->mapSetFloat(map1, "src_height", d.dd.active_height, maReplace); + vsapi->mapSetInt(map1, "force", force, maReplace); + vsapi->mapSetInt(map1, "force_h", force_h, maReplace); + vsapi->mapSetInt(map1, "force_v", force_v, maReplace); vsapi->mapSetInt(map1, "opt", (int)opt_enum, maReplace); map2 = vsapi->invoke(descale_plugin, "Descale", map1); vsapi->freeNode(tmp_node); @@ -400,6 +496,9 @@ VS_EXTERNAL_API(void) VapourSynthPluginInit2(VSPlugin *plugin, const VSPLUGINAPI "src_top:float:opt;" "src_width:float:opt;" "src_height:float:opt;" + "force:int:opt;" + "force_h:int:opt;" + "force_v:int:opt;" "opt:int:opt;", "clip:vnode;", descale_create, (void *)(DESCALE_MODE_BICUBIC), plugin); @@ -413,6 +512,9 @@ VS_EXTERNAL_API(void) VapourSynthPluginInit2(VSPlugin *plugin, const VSPLUGINAPI "src_top:float:opt;" "src_width:float:opt;" "src_height:float:opt;" + "force:int:opt;" + "force_h:int:opt;" + "force_v:int:opt;" "opt:int:opt;", "clip:vnode;", descale_create, (void *)(DESCALE_MODE_LANCZOS), plugin); @@ -425,6 +527,9 @@ VS_EXTERNAL_API(void) VapourSynthPluginInit2(VSPlugin *plugin, const VSPLUGINAPI "src_top:float:opt;" "src_width:float:opt;" "src_height:float:opt;" + "force:int:opt;" + "force_h:int:opt;" + "force_v:int:opt;" "opt:int:opt;", "clip:vnode;", descale_create, (void *)(DESCALE_MODE_SPLINE16), plugin); @@ -437,6 +542,9 @@ VS_EXTERNAL_API(void) VapourSynthPluginInit2(VSPlugin *plugin, const VSPLUGINAPI "src_top:float:opt;" "src_width:float:opt;" "src_height:float:opt;" + "force:int:opt;" + "force_h:int:opt;" + "force_v:int:opt;" "opt:int:opt;", "clip:vnode;", descale_create, (void *)(DESCALE_MODE_SPLINE36), plugin); @@ -449,6 +557,9 @@ VS_EXTERNAL_API(void) VapourSynthPluginInit2(VSPlugin *plugin, const VSPLUGINAPI "src_top:float:opt;" "src_width:float:opt;" "src_height:float:opt;" + "force:int:opt;" + "force_h:int:opt;" + "force_v:int:opt;" "opt:int:opt;", "clip:vnode;", descale_create, (void *)(DESCALE_MODE_SPLINE64), plugin); @@ -457,7 +568,8 @@ VS_EXTERNAL_API(void) VapourSynthPluginInit2(VSPlugin *plugin, const VSPLUGINAPI "src:vnode;" "width:int;" "height:int;" - "kernel:data;" + "kernel:data:opt;" + "custom_kernel:func:opt;" "taps:int:opt;" "b:float:opt;" "c:float:opt;" @@ -465,6 +577,9 @@ VS_EXTERNAL_API(void) VapourSynthPluginInit2(VSPlugin *plugin, const VSPLUGINAPI "src_top:float:opt;" "src_width:float:opt;" "src_height:float:opt;" + "force:int:opt;" + "force_h:int:opt;" + "force_v:int:opt;" "opt:int:opt;", "clip:vnode;", descale_create, NULL, plugin);