[FFmpeg-devel] avfilter: add lut1d filter

Submitted by Paul B Mahol on Aug. 23, 2018, 12:15 p.m.

Details

Message ID 20180823121506.7599-1-onemda@gmail.com
State New
Headers show

Commit Message

Paul B Mahol Aug. 23, 2018, 12:15 p.m.
Signed-off-by: Paul B Mahol <onemda@gmail.com>
---
 doc/filters.texi         |  31 +++
 libavfilter/Makefile     |   1 +
 libavfilter/allfilters.c |   1 +
 libavfilter/vf_lut3d.c   | 438 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 471 insertions(+)

Comments

Paul B Mahol Sept. 7, 2018, 11:59 a.m.
On 8/23/18, Paul B Mahol <onemda@gmail.com> wrote:
> Signed-off-by: Paul B Mahol <onemda@gmail.com>
> ---
>  doc/filters.texi         |  31 +++
>  libavfilter/Makefile     |   1 +
>  libavfilter/allfilters.c |   1 +
>  libavfilter/vf_lut3d.c   | 438 +++++++++++++++++++++++++++++++++++++++
>  4 files changed, 471 insertions(+)
>

Will apply.

Patch hide | download patch | download mbox

diff --git a/doc/filters.texi b/doc/filters.texi
index 32c95b591c..37e79d34e1 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -10962,6 +10962,37 @@  Set maximal size in number of frames. Default is 0.
 Set first frame of loop. Default is 0.
 @end table
 
+@section lut1d
+
+Apply a 1D LUT to an input video.
+
+The filter accepts the following options:
+
+@table @option
+@item file
+Set the 1D LUT file name.
+
+Currently supported formats:
+@table @samp
+@item cube
+Iridas
+@end table
+
+@item interp
+Select interpolation mode.
+
+Available values are:
+
+@table @samp
+@item nearest
+Use values from the nearest defined point.
+@item linear
+Interpolate values using the linear interpolation.
+@item cubic
+Interpolate values using the cubic interpolation.
+@end table
+@end table
+
 @anchor{lut3d}
 @section lut3d
 
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index e5d3a57af7..e412000c8f 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -258,6 +258,7 @@  OBJS-$(CONFIG_LIBVMAF_FILTER)                += vf_libvmaf.o framesync.o
 OBJS-$(CONFIG_LIMITER_FILTER)                += vf_limiter.o
 OBJS-$(CONFIG_LOOP_FILTER)                   += f_loop.o
 OBJS-$(CONFIG_LUMAKEY_FILTER)                += vf_lumakey.o
+OBJS-$(CONFIG_LUT1D_FILTER)                  += vf_lut3d.o
 OBJS-$(CONFIG_LUT_FILTER)                    += vf_lut.o
 OBJS-$(CONFIG_LUT2_FILTER)                   += vf_lut2.o framesync.o
 OBJS-$(CONFIG_LUT3D_FILTER)                  += vf_lut3d.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 9732ae5345..2fa9460335 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -246,6 +246,7 @@  extern AVFilter ff_vf_limiter;
 extern AVFilter ff_vf_loop;
 extern AVFilter ff_vf_lumakey;
 extern AVFilter ff_vf_lut;
+extern AVFilter ff_vf_lut1d;
 extern AVFilter ff_vf_lut2;
 extern AVFilter ff_vf_lut3d;
 extern AVFilter ff_vf_lutrgb;
diff --git a/libavfilter/vf_lut3d.c b/libavfilter/vf_lut3d.c
index 27b79b860b..6ec0cad650 100644
--- a/libavfilter/vf_lut3d.c
+++ b/libavfilter/vf_lut3d.c
@@ -1,5 +1,6 @@ 
 /*
  * Copyright (c) 2013 Clément Bœsch
+ * Copyright (c) 2018 Paul B Mahol
  *
  * This file is part of FFmpeg.
  *
@@ -975,3 +976,440 @@  AVFilter ff_vf_haldclut = {
     .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL | AVFILTER_FLAG_SLICE_THREADS,
 };
 #endif
+
+#if CONFIG_LUT1D_FILTER
+
+enum interp_1d_mode {
+    INTERPOLATE_1D_NEAREST,
+    INTERPOLATE_1D_LINEAR,
+    INTERPOLATE_1D_CUBIC,
+    NB_INTERP_1D_MODE
+};
+
+#define MAX_1D_LEVEL 65536
+
+typedef struct LUT1DContext {
+    const AVClass *class;
+    char *file;
+    int interpolation;          ///<interp_1d_mode
+    uint8_t rgba_map[4];
+    int step;
+    float lut[3][MAX_1D_LEVEL];
+    int lutsize;
+    avfilter_action_func *interp;
+} LUT1DContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(LUT1DContext, x)
+
+static void set_identity_matrix_1d(LUT1DContext *lut1d, int size)
+{
+    const float c = 1. / (size - 1);
+    int i;
+
+    lut1d->lutsize = size;
+    for (i = 0; i < size; i++) {
+        lut1d->lut[0][i] = i * c;
+        lut1d->lut[1][i] = i * c;
+        lut1d->lut[2][i] = i * c;
+    }
+}
+
+static int parse_cube_1d(AVFilterContext *ctx, FILE *f)
+{
+    LUT1DContext *lut1d = ctx->priv;
+    char line[MAX_LINE_SIZE];
+    float min[3] = {0.0, 0.0, 0.0};
+    float max[3] = {1.0, 1.0, 1.0};
+
+    while (fgets(line, sizeof(line), f)) {
+        if (!strncmp(line, "LUT_1D_SIZE ", 12)) {
+            const int size = strtol(line + 12, NULL, 0);
+            int i;
+
+            if (size < 2 || size > MAX_1D_LEVEL) {
+                av_log(ctx, AV_LOG_ERROR, "Too large or invalid 1D LUT size\n");
+                return AVERROR(EINVAL);
+            }
+            lut1d->lutsize = size;
+            for (i = 0; i < size; i++) {
+                do {
+try_again:
+                    NEXT_LINE(0);
+                    if (!strncmp(line, "DOMAIN_", 7)) {
+                        float *vals = NULL;
+                        if      (!strncmp(line + 7, "MIN ", 4)) vals = min;
+                        else if (!strncmp(line + 7, "MAX ", 4)) vals = max;
+                        if (!vals)
+                            return AVERROR_INVALIDDATA;
+                        sscanf(line + 11, "%f %f %f", vals, vals + 1, vals + 2);
+                        av_log(ctx, AV_LOG_DEBUG, "min: %f %f %f | max: %f %f %f\n",
+                               min[0], min[1], min[2], max[0], max[1], max[2]);
+                        goto try_again;
+                    }
+                } while (skip_line(line));
+                if (sscanf(line, "%f %f %f", &lut1d->lut[0][i], &lut1d->lut[1][i], &lut1d->lut[2][i]) != 3)
+                    return AVERROR_INVALIDDATA;
+                lut1d->lut[0][i] *= max[0] - min[0];
+                lut1d->lut[1][i] *= max[1] - min[1];
+                lut1d->lut[2][i] *= max[2] - min[2];
+            }
+            break;
+        }
+    }
+    return 0;
+}
+
+static const AVOption lut1d_options[] = {
+    { "file", "set 1D LUT file name", OFFSET(file), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
+    { "interp", "select interpolation mode", OFFSET(interpolation),    AV_OPT_TYPE_INT, {.i64=INTERPOLATE_1D_LINEAR}, 0, NB_INTERP_1D_MODE-1, FLAGS, "interp_mode" },
+        { "nearest", "use values from the nearest defined points", 0, AV_OPT_TYPE_CONST, {.i64=INTERPOLATE_1D_NEAREST},   INT_MIN, INT_MAX, FLAGS, "interp_mode" },
+        { "linear",  "use values from the linear interpolation",   0, AV_OPT_TYPE_CONST, {.i64=INTERPOLATE_1D_LINEAR},    INT_MIN, INT_MAX, FLAGS, "interp_mode" },
+        { "cubic",   "use values from the cubic interpolation",    0, AV_OPT_TYPE_CONST, {.i64=INTERPOLATE_1D_CUBIC},     INT_MIN, INT_MAX, FLAGS, "interp_mode" },
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(lut1d);
+
+static inline float interp_1d_nearest(const LUT1DContext *lut1d,
+                                      int idx, const float s)
+{
+    return lut1d->lut[idx][NEAR(s)];
+}
+
+#define NEXT1D(x) (FFMIN((int)(x) + 1, lut1d->lutsize - 1))
+
+static inline float interp_1d_linear(const LUT1DContext *lut1d,
+                                     int idx, const float s)
+{
+    const int prev = PREV(s);
+    const int next = NEXT1D(s);
+    const float d = s - prev;
+    const float p = lut1d->lut[idx][prev];
+    const float n = lut1d->lut[idx][next];
+
+    return lerpf(p, n, d);
+}
+
+static inline float interp_1d_cubic(const LUT1DContext *lut1d,
+                                    int idx, const float s)
+{
+    const int prev = PREV(s);
+    const int next = NEXT1D(s);
+    const float mu = s - prev;
+    float a0, a1, a2, a3, mu2;
+
+    float y0 = lut1d->lut[idx][FFMAX(prev - 1, 0)];
+    float y1 = lut1d->lut[idx][prev];
+    float y2 = lut1d->lut[idx][next];
+    float y3 = lut1d->lut[idx][FFMIN(next + 1, lut1d->lutsize - 1)];
+
+
+    mu2 = mu * mu;
+    a0 = y3 - y2 - y0 + y1;
+    a1 = y0 - y1 - a0;
+    a2 = y2 - y0;
+    a3 = y1;
+
+    return a0 * mu * mu2 + a1 * mu2 + a2 * mu + a3;
+}
+
+#define DEFINE_INTERP_FUNC_PLANAR_1D(name, nbits, depth)                                                  \
+static int interp_1d_##nbits##_##name##_p##depth(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) \
+{                                                                                                      \
+    int x, y;                                                                                          \
+    const LUT1DContext *lut1d = ctx->priv;                                                             \
+    const ThreadData *td = arg;                                                                        \
+    const AVFrame *in  = td->in;                                                                       \
+    const AVFrame *out = td->out;                                                                      \
+    const int direct = out == in;                                                                      \
+    const int slice_start = (in->height *  jobnr   ) / nb_jobs;                                        \
+    const int slice_end   = (in->height * (jobnr+1)) / nb_jobs;                                        \
+    uint8_t *grow = out->data[0] + slice_start * out->linesize[0];                                     \
+    uint8_t *brow = out->data[1] + slice_start * out->linesize[1];                                     \
+    uint8_t *rrow = out->data[2] + slice_start * out->linesize[2];                                     \
+    uint8_t *arow = out->data[3] + slice_start * out->linesize[3];                                     \
+    const uint8_t *srcgrow = in->data[0] + slice_start * in->linesize[0];                              \
+    const uint8_t *srcbrow = in->data[1] + slice_start * in->linesize[1];                              \
+    const uint8_t *srcrrow = in->data[2] + slice_start * in->linesize[2];                              \
+    const uint8_t *srcarow = in->data[3] + slice_start * in->linesize[3];                              \
+    const float scale = (1. / ((1<<depth) - 1)) * (lut1d->lutsize - 1);                                \
+                                                                                                       \
+    for (y = slice_start; y < slice_end; y++) {                                                        \
+        uint##nbits##_t *dstg = (uint##nbits##_t *)grow;                                               \
+        uint##nbits##_t *dstb = (uint##nbits##_t *)brow;                                               \
+        uint##nbits##_t *dstr = (uint##nbits##_t *)rrow;                                               \
+        uint##nbits##_t *dsta = (uint##nbits##_t *)arow;                                               \
+        const uint##nbits##_t *srcg = (const uint##nbits##_t *)srcgrow;                                \
+        const uint##nbits##_t *srcb = (const uint##nbits##_t *)srcbrow;                                \
+        const uint##nbits##_t *srcr = (const uint##nbits##_t *)srcrrow;                                \
+        const uint##nbits##_t *srca = (const uint##nbits##_t *)srcarow;                                \
+        for (x = 0; x < in->width; x++) {                                                              \
+            float r = srcr[x] * scale;                                                                 \
+            float g = srcg[x] * scale;                                                                 \
+            float b = srcb[x] * scale;                                                                 \
+            r = interp_1d_##name(lut1d, 0, r);                                                         \
+            g = interp_1d_##name(lut1d, 1, g);                                                         \
+            b = interp_1d_##name(lut1d, 2, b);                                                         \
+            dstr[x] = av_clip_uintp2(r * (float)((1<<depth) - 1), depth);                              \
+            dstg[x] = av_clip_uintp2(g * (float)((1<<depth) - 1), depth);                              \
+            dstb[x] = av_clip_uintp2(b * (float)((1<<depth) - 1), depth);                              \
+            if (!direct && in->linesize[3])                                                            \
+                dsta[x] = srca[x];                                                                     \
+        }                                                                                              \
+        grow += out->linesize[0];                                                                      \
+        brow += out->linesize[1];                                                                      \
+        rrow += out->linesize[2];                                                                      \
+        arow += out->linesize[3];                                                                      \
+        srcgrow += in->linesize[0];                                                                    \
+        srcbrow += in->linesize[1];                                                                    \
+        srcrrow += in->linesize[2];                                                                    \
+        srcarow += in->linesize[3];                                                                    \
+    }                                                                                                  \
+    return 0;                                                                                          \
+}
+
+DEFINE_INTERP_FUNC_PLANAR_1D(nearest,     8, 8)
+DEFINE_INTERP_FUNC_PLANAR_1D(linear,      8, 8)
+DEFINE_INTERP_FUNC_PLANAR_1D(cubic,       8, 8)
+
+DEFINE_INTERP_FUNC_PLANAR_1D(nearest,     16, 9)
+DEFINE_INTERP_FUNC_PLANAR_1D(linear,      16, 9)
+DEFINE_INTERP_FUNC_PLANAR_1D(cubic,       16, 9)
+
+DEFINE_INTERP_FUNC_PLANAR_1D(nearest,     16, 10)
+DEFINE_INTERP_FUNC_PLANAR_1D(linear,      16, 10)
+DEFINE_INTERP_FUNC_PLANAR_1D(cubic,       16, 10)
+
+DEFINE_INTERP_FUNC_PLANAR_1D(nearest,     16, 12)
+DEFINE_INTERP_FUNC_PLANAR_1D(linear,      16, 12)
+DEFINE_INTERP_FUNC_PLANAR_1D(cubic,       16, 12)
+
+DEFINE_INTERP_FUNC_PLANAR_1D(nearest,     16, 14)
+DEFINE_INTERP_FUNC_PLANAR_1D(linear,      16, 14)
+DEFINE_INTERP_FUNC_PLANAR_1D(cubic,       16, 14)
+
+DEFINE_INTERP_FUNC_PLANAR_1D(nearest,     16, 16)
+DEFINE_INTERP_FUNC_PLANAR_1D(linear,      16, 16)
+DEFINE_INTERP_FUNC_PLANAR_1D(cubic,       16, 16)
+
+#define DEFINE_INTERP_FUNC_1D(name, nbits)                                                          \
+static int interp_1d_##nbits##_##name(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)      \
+{                                                                                                   \
+    int x, y;                                                                                       \
+    const LUT1DContext *lut1d = ctx->priv;                                                          \
+    const ThreadData *td = arg;                                                                     \
+    const AVFrame *in  = td->in;                                                                    \
+    const AVFrame *out = td->out;                                                                   \
+    const int direct = out == in;                                                                   \
+    const int step = lut1d->step;                                                                   \
+    const uint8_t r = lut1d->rgba_map[R];                                                           \
+    const uint8_t g = lut1d->rgba_map[G];                                                           \
+    const uint8_t b = lut1d->rgba_map[B];                                                           \
+    const uint8_t a = lut1d->rgba_map[A];                                                           \
+    const int slice_start = (in->height *  jobnr   ) / nb_jobs;                                     \
+    const int slice_end   = (in->height * (jobnr+1)) / nb_jobs;                                     \
+    uint8_t       *dstrow = out->data[0] + slice_start * out->linesize[0];                          \
+    const uint8_t *srcrow = in ->data[0] + slice_start * in ->linesize[0];                          \
+    const float scale = (1. / ((1<<nbits) - 1)) * (lut1d->lutsize - 1);                             \
+                                                                                                    \
+    for (y = slice_start; y < slice_end; y++) {                                                     \
+        uint##nbits##_t *dst = (uint##nbits##_t *)dstrow;                                           \
+        const uint##nbits##_t *src = (const uint##nbits##_t *)srcrow;                               \
+        for (x = 0; x < in->width * step; x += step) {                                              \
+            float rr = src[x + r] * scale;                                                          \
+            float gg = src[x + g] * scale;                                                          \
+            float bb = src[x + b] * scale;                                                          \
+            rr = interp_1d_##name(lut1d, 0, rr);                                                    \
+            gg = interp_1d_##name(lut1d, 1, gg);                                                    \
+            bb = interp_1d_##name(lut1d, 2, bb);                                                    \
+            dst[x + r] = av_clip_uint##nbits(rr * (float)((1<<nbits) - 1));                         \
+            dst[x + g] = av_clip_uint##nbits(gg * (float)((1<<nbits) - 1));                         \
+            dst[x + b] = av_clip_uint##nbits(bb * (float)((1<<nbits) - 1));                         \
+            if (!direct && step == 4)                                                               \
+                dst[x + a] = src[x + a];                                                            \
+        }                                                                                           \
+        dstrow += out->linesize[0];                                                                 \
+        srcrow += in ->linesize[0];                                                                 \
+    }                                                                                               \
+    return 0;                                                                                       \
+}
+
+DEFINE_INTERP_FUNC_1D(nearest,     8)
+DEFINE_INTERP_FUNC_1D(linear,      8)
+DEFINE_INTERP_FUNC_1D(cubic,       8)
+
+DEFINE_INTERP_FUNC_1D(nearest,     16)
+DEFINE_INTERP_FUNC_1D(linear,      16)
+DEFINE_INTERP_FUNC_1D(cubic,       16)
+
+static int config_input_1d(AVFilterLink *inlink)
+{
+    int depth, is16bit = 0, planar = 0;
+    LUT1DContext *lut1d = inlink->dst->priv;
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
+
+    depth = desc->comp[0].depth;
+
+    switch (inlink->format) {
+    case AV_PIX_FMT_RGB48:
+    case AV_PIX_FMT_BGR48:
+    case AV_PIX_FMT_RGBA64:
+    case AV_PIX_FMT_BGRA64:
+        is16bit = 1;
+        break;
+    case AV_PIX_FMT_GBRP9:
+    case AV_PIX_FMT_GBRP10:
+    case AV_PIX_FMT_GBRP12:
+    case AV_PIX_FMT_GBRP14:
+    case AV_PIX_FMT_GBRP16:
+    case AV_PIX_FMT_GBRAP10:
+    case AV_PIX_FMT_GBRAP12:
+    case AV_PIX_FMT_GBRAP16:
+        is16bit = 1;
+    case AV_PIX_FMT_GBRP:
+    case AV_PIX_FMT_GBRAP:
+        planar = 1;
+        break;
+    }
+
+    ff_fill_rgba_map(lut1d->rgba_map, inlink->format);
+    lut1d->step = av_get_padded_bits_per_pixel(desc) >> (3 + is16bit);
+
+#define SET_FUNC_1D(name) do {                                     \
+    if (planar) {                                                  \
+        switch (depth) {                                           \
+        case  8: lut1d->interp = interp_1d_8_##name##_p8;   break; \
+        case  9: lut1d->interp = interp_1d_16_##name##_p9;  break; \
+        case 10: lut1d->interp = interp_1d_16_##name##_p10; break; \
+        case 12: lut1d->interp = interp_1d_16_##name##_p12; break; \
+        case 14: lut1d->interp = interp_1d_16_##name##_p14; break; \
+        case 16: lut1d->interp = interp_1d_16_##name##_p16; break; \
+        }                                                          \
+    } else if (is16bit) { lut1d->interp = interp_1d_16_##name;     \
+    } else {              lut1d->interp = interp_1d_8_##name; }    \
+} while (0)
+
+    switch (lut1d->interpolation) {
+    case INTERPOLATE_1D_NEAREST:     SET_FUNC_1D(nearest);  break;
+    case INTERPOLATE_1D_LINEAR:      SET_FUNC_1D(linear);   break;
+    case INTERPOLATE_1D_CUBIC:       SET_FUNC_1D(cubic);    break;
+    default:
+        av_assert0(0);
+    }
+
+    return 0;
+}
+
+static av_cold int lut1d_init(AVFilterContext *ctx)
+{
+    int ret;
+    FILE *f;
+    const char *ext;
+    LUT1DContext *lut1d = ctx->priv;
+
+    if (!lut1d->file) {
+        set_identity_matrix_1d(lut1d, 32);
+        return 0;
+    }
+
+    f = fopen(lut1d->file, "r");
+    if (!f) {
+        ret = AVERROR(errno);
+        av_log(ctx, AV_LOG_ERROR, "%s: %s\n", lut1d->file, av_err2str(ret));
+        return ret;
+    }
+
+    ext = strrchr(lut1d->file, '.');
+    if (!ext) {
+        av_log(ctx, AV_LOG_ERROR, "Unable to guess the format from the extension\n");
+        ret = AVERROR_INVALIDDATA;
+        goto end;
+    }
+    ext++;
+
+    if (!av_strcasecmp(ext, "cube") || !av_strcasecmp(ext, "1dlut")) {
+        ret = parse_cube_1d(ctx, f);
+    } else {
+        av_log(ctx, AV_LOG_ERROR, "Unrecognized '.%s' file type\n", ext);
+        ret = AVERROR(EINVAL);
+    }
+
+    if (!ret && !lut1d->lutsize) {
+        av_log(ctx, AV_LOG_ERROR, "1D LUT is empty\n");
+        ret = AVERROR_INVALIDDATA;
+    }
+
+end:
+    fclose(f);
+    return ret;
+}
+
+static AVFrame *apply_1d_lut(AVFilterLink *inlink, AVFrame *in)
+{
+    AVFilterContext *ctx = inlink->dst;
+    LUT1DContext *lut1d = ctx->priv;
+    AVFilterLink *outlink = inlink->dst->outputs[0];
+    AVFrame *out;
+    ThreadData td;
+
+    if (av_frame_is_writable(in)) {
+        out = in;
+    } else {
+        out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
+        if (!out) {
+            av_frame_free(&in);
+            return NULL;
+        }
+        av_frame_copy_props(out, in);
+    }
+
+    td.in  = in;
+    td.out = out;
+    ctx->internal->execute(ctx, lut1d->interp, &td, NULL, FFMIN(outlink->h, ff_filter_get_nb_threads(ctx)));
+
+    if (out != in)
+        av_frame_free(&in);
+
+    return out;
+}
+
+static int filter_frame_1d(AVFilterLink *inlink, AVFrame *in)
+{
+    AVFilterLink *outlink = inlink->dst->outputs[0];
+    AVFrame *out = apply_1d_lut(inlink, in);
+    if (!out)
+        return AVERROR(ENOMEM);
+    return ff_filter_frame(outlink, out);
+}
+
+static const AVFilterPad lut1d_inputs[] = {
+    {
+        .name         = "default",
+        .type         = AVMEDIA_TYPE_VIDEO,
+        .filter_frame = filter_frame_1d,
+        .config_props = config_input_1d,
+    },
+    { NULL }
+};
+
+static const AVFilterPad lut1d_outputs[] = {
+    {
+        .name = "default",
+        .type = AVMEDIA_TYPE_VIDEO,
+    },
+    { NULL }
+};
+
+AVFilter ff_vf_lut1d = {
+    .name          = "lut1d",
+    .description   = NULL_IF_CONFIG_SMALL("Adjust colors using a 1D LUT."),
+    .priv_size     = sizeof(LUT1DContext),
+    .init          = lut1d_init,
+    .query_formats = query_formats,
+    .inputs        = lut1d_inputs,
+    .outputs       = lut1d_outputs,
+    .priv_class    = &lut1d_class,
+    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS,
+};
+#endif