Browse code

lavfi: add lut3d filter.

Fixes part of Ticket #2517.

Clément Bœsch authored on 2013/05/07 21:48:56
Showing 6 changed files
... ...
@@ -52,6 +52,7 @@ version <next>:
52 52
 - Escape 130 video decoder
53 53
 - FTP protocol support
54 54
 - V4L2 output device
55
+- 3D LUT filter (lut3d)
55 56
 
56 57
 
57 58
 version 1.2:
... ...
@@ -4596,6 +4596,42 @@ kerndeint=map=1
4596 4596
 @end example
4597 4597
 @end itemize
4598 4598
 
4599
+@section lut3d
4600
+
4601
+Apply a 3D LUT to an input video.
4602
+
4603
+The filter accepts the following options:
4604
+
4605
+@table @option
4606
+@item file
4607
+Set the 3D LUT file name.
4608
+
4609
+Currently supported formats:
4610
+@table @samp
4611
+@item 3dl
4612
+AfterEffects
4613
+@item cube
4614
+Iridas
4615
+@item dat
4616
+DaVinci
4617
+@item m3d
4618
+Pandora
4619
+@end table
4620
+@item interp
4621
+Select interpolation mode.
4622
+
4623
+Available values are:
4624
+
4625
+@table @samp
4626
+@item nearest
4627
+Use values from the nearest defined point.
4628
+@item trilinear
4629
+Interpolate values using the 8 points defining a cube.
4630
+@item tetrahedral
4631
+Interpolate values using a tetrahedron.
4632
+@end table
4633
+@end table
4634
+
4599 4635
 @section lut, lutrgb, lutyuv
4600 4636
 
4601 4637
 Compute a look-up table for binding each pixel component input value
... ...
@@ -145,6 +145,7 @@ OBJS-$(CONFIG_IL_FILTER)                     += vf_il.o
145 145
 OBJS-$(CONFIG_INTERLACE_FILTER)              += vf_interlace.o
146 146
 OBJS-$(CONFIG_INTERLEAVE_FILTER)             += f_interleave.o
147 147
 OBJS-$(CONFIG_KERNDEINT_FILTER)              += vf_kerndeint.o
148
+OBJS-$(CONFIG_LUT3D_FILTER)                  += vf_lut3d.o
148 149
 OBJS-$(CONFIG_LUT_FILTER)                    += vf_lut.o
149 150
 OBJS-$(CONFIG_LUTRGB_FILTER)                 += vf_lut.o
150 151
 OBJS-$(CONFIG_LUTYUV_FILTER)                 += vf_lut.o
... ...
@@ -143,6 +143,7 @@ void avfilter_register_all(void)
143 143
     REGISTER_FILTER(INTERLACE,      interlace,      vf);
144 144
     REGISTER_FILTER(INTERLEAVE,     interleave,     vf);
145 145
     REGISTER_FILTER(KERNDEINT,      kerndeint,      vf);
146
+    REGISTER_FILTER(LUT3D,          lut3d,          vf);
146 147
     REGISTER_FILTER(LUT,            lut,            vf);
147 148
     REGISTER_FILTER(LUTRGB,         lutrgb,         vf);
148 149
     REGISTER_FILTER(LUTYUV,         lutyuv,         vf);
... ...
@@ -30,8 +30,8 @@
30 30
 #include "libavutil/avutil.h"
31 31
 
32 32
 #define LIBAVFILTER_VERSION_MAJOR  3
33
-#define LIBAVFILTER_VERSION_MINOR  68
34
-#define LIBAVFILTER_VERSION_MICRO 103
33
+#define LIBAVFILTER_VERSION_MINOR  69
34
+#define LIBAVFILTER_VERSION_MICRO 100
35 35
 
36 36
 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
37 37
                                                LIBAVFILTER_VERSION_MINOR, \
38 38
new file mode 100644
... ...
@@ -0,0 +1,578 @@
0
+/*
1
+ * Copyright (c) 2013 Clément Bœsch
2
+ *
3
+ * This file is part of FFmpeg.
4
+ *
5
+ * FFmpeg is free software; you can redistribute it and/or
6
+ * modify it under the terms of the GNU Lesser General Public
7
+ * License as published by the Free Software Foundation; either
8
+ * version 2.1 of the License, or (at your option) any later version.
9
+ *
10
+ * FFmpeg is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
+ * Lesser General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Lesser General Public
16
+ * License along with FFmpeg; if not, write to the Free Software
17
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+ */
19
+
20
+/**
21
+ * @file
22
+ * 3D Lookup table filter
23
+ */
24
+
25
+#include "libavutil/opt.h"
26
+#include "libavutil/file.h"
27
+#include "libavutil/intreadwrite.h"
28
+#include "libavutil/avassert.h"
29
+#include "libavutil/pixdesc.h"
30
+#include "libavutil/avstring.h"
31
+#include "avfilter.h"
32
+#include "drawutils.h"
33
+#include "formats.h"
34
+#include "internal.h"
35
+#include "video.h"
36
+
37
+#define R 0
38
+#define G 1
39
+#define B 2
40
+#define A 3
41
+
42
+enum interp_mode {
43
+    INTERPOLATE_NEAREST,
44
+    INTERPOLATE_TRILINEAR,
45
+    INTERPOLATE_TETRAHEDRAL,
46
+    NB_INTERP_MODE
47
+};
48
+
49
+struct rgbvec {
50
+    float r, g, b;
51
+};
52
+
53
+#define MAX_LEVEL 36
54
+
55
+typedef struct LUT3DContext {
56
+    const AVClass *class;
57
+    enum interp_mode interpolation;
58
+    char *file;
59
+    uint8_t rgba_map[4];
60
+    int step;
61
+    int is16bit;
62
+    struct rgbvec (*interp_8) (const struct LUT3DContext*, uint8_t,  uint8_t,  uint8_t);
63
+    struct rgbvec (*interp_16)(const struct LUT3DContext*, uint16_t, uint16_t, uint16_t);
64
+    struct rgbvec lut[MAX_LEVEL][MAX_LEVEL][MAX_LEVEL];
65
+    int lutsize;
66
+} LUT3DContext;
67
+
68
+#define OFFSET(x) offsetof(LUT3DContext, x)
69
+#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
70
+static const AVOption lut3d_options[] = {
71
+    { "file",   "set 3D LUT file name", OFFSET(file), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
72
+    { "interp", "select interpolation mode", OFFSET(interpolation), AV_OPT_TYPE_INT, {.i64=INTERPOLATE_TETRAHEDRAL}, 0, NB_INTERP_MODE-1, FLAGS, "interp_mode" },
73
+        { "nearest",     "use values from the nearest defined points",            0, AV_OPT_TYPE_CONST, {.i64=INTERPOLATE_NEAREST},     INT_MIN, INT_MAX, FLAGS, "interp_mode" },
74
+        { "trilinear",   "interpolate values using the 8 points defining a cube", 0, AV_OPT_TYPE_CONST, {.i64=INTERPOLATE_TRILINEAR},   INT_MIN, INT_MAX, FLAGS, "interp_mode" },
75
+        { "tetrahedral", "interpolate values using a tetrahedron",                0, AV_OPT_TYPE_CONST, {.i64=INTERPOLATE_TETRAHEDRAL}, INT_MIN, INT_MAX, FLAGS, "interp_mode" },
76
+    { NULL }
77
+};
78
+
79
+AVFILTER_DEFINE_CLASS(lut3d);
80
+
81
+static inline float lerpf(float v0, float v1, float f)
82
+{
83
+    return v0 + (v1 - v0) * f;
84
+}
85
+
86
+static inline struct rgbvec lerp(const struct rgbvec *v0, const struct rgbvec *v1, float f)
87
+{
88
+    struct rgbvec v = {
89
+        lerpf(v0->r, v1->r, f), lerpf(v0->g, v1->g, f), lerpf(v0->b, v1->b, f)
90
+    };
91
+    return v;
92
+}
93
+
94
+#define NEAR(x) ((int)((x) + .5))
95
+#define PREV(x) ((int)(x))
96
+#define NEXT(x) ((int)(x) + 1)
97
+
98
+/**
99
+ * Get the nearest defined point
100
+ */
101
+static inline struct rgbvec interp_nearest(const LUT3DContext *lut3d,
102
+                                           const struct rgbvec *s)
103
+{
104
+    return lut3d->lut[NEAR(s->r)][NEAR(s->g)][NEAR(s->b)];
105
+}
106
+
107
+/**
108
+ * Interpolate using the 8 vertices of a cube
109
+ * @see https://en.wikipedia.org/wiki/Trilinear_interpolation
110
+ */
111
+static inline struct rgbvec interp_trilinear(const LUT3DContext *lut3d,
112
+                                             const struct rgbvec *s)
113
+{
114
+    const struct rgbvec d = {s->r - PREV(s->r), s->g - PREV(s->g), s->b - PREV(s->b)};
115
+    const struct rgbvec c000 = lut3d->lut[PREV(s->r)][PREV(s->g)][PREV(s->b)];
116
+    const struct rgbvec c001 = lut3d->lut[PREV(s->r)][PREV(s->g)][NEXT(s->b)];
117
+    const struct rgbvec c010 = lut3d->lut[PREV(s->r)][NEXT(s->g)][PREV(s->b)];
118
+    const struct rgbvec c011 = lut3d->lut[PREV(s->r)][NEXT(s->g)][NEXT(s->b)];
119
+    const struct rgbvec c100 = lut3d->lut[NEXT(s->r)][PREV(s->g)][PREV(s->b)];
120
+    const struct rgbvec c101 = lut3d->lut[NEXT(s->r)][PREV(s->g)][NEXT(s->b)];
121
+    const struct rgbvec c110 = lut3d->lut[NEXT(s->r)][NEXT(s->g)][PREV(s->b)];
122
+    const struct rgbvec c111 = lut3d->lut[NEXT(s->r)][NEXT(s->g)][NEXT(s->b)];
123
+    const struct rgbvec c00  = lerp(&c000, &c100, d.r);
124
+    const struct rgbvec c10  = lerp(&c010, &c110, d.r);
125
+    const struct rgbvec c01  = lerp(&c001, &c101, d.r);
126
+    const struct rgbvec c11  = lerp(&c011, &c111, d.r);
127
+    const struct rgbvec c0   = lerp(&c00,  &c10,  d.g);
128
+    const struct rgbvec c1   = lerp(&c01,  &c11,  d.g);
129
+    const struct rgbvec c    = lerp(&c0,   &c1,   d.b);
130
+    return c;
131
+}
132
+
133
+/**
134
+ * Tetrahedral interpolation. Based on code found in Truelight Software Library paper.
135
+ * @see http://www.filmlight.ltd.uk/pdf/whitepapers/FL-TL-TN-0057-SoftwareLib.pdf
136
+ */
137
+static inline struct rgbvec interp_tetrahedral(const LUT3DContext *lut3d,
138
+                                               const struct rgbvec *s)
139
+{
140
+    const struct rgbvec d = {s->r - PREV(s->r), s->g - PREV(s->g), s->b - PREV(s->b)};
141
+    const struct rgbvec c000 = lut3d->lut[PREV(s->r)][PREV(s->g)][PREV(s->b)];
142
+    const struct rgbvec c001 = lut3d->lut[PREV(s->r)][PREV(s->g)][NEXT(s->b)];
143
+    const struct rgbvec c010 = lut3d->lut[PREV(s->r)][NEXT(s->g)][PREV(s->b)];
144
+    const struct rgbvec c011 = lut3d->lut[PREV(s->r)][NEXT(s->g)][NEXT(s->b)];
145
+    const struct rgbvec c100 = lut3d->lut[NEXT(s->r)][PREV(s->g)][PREV(s->b)];
146
+    const struct rgbvec c101 = lut3d->lut[NEXT(s->r)][PREV(s->g)][NEXT(s->b)];
147
+    const struct rgbvec c110 = lut3d->lut[NEXT(s->r)][NEXT(s->g)][PREV(s->b)];
148
+    const struct rgbvec c111 = lut3d->lut[NEXT(s->r)][NEXT(s->g)][NEXT(s->b)];
149
+    struct rgbvec c;
150
+    if (d.r > d.g) {
151
+        if (d.g > d.b) {
152
+            c.r = (1-d.r) * c000.r + (d.r-d.g) * c100.r + (d.g-d.b) * c110.r + (d.b) * c111.r;
153
+            c.g = (1-d.r) * c000.g + (d.r-d.g) * c100.g + (d.g-d.b) * c110.g + (d.b) * c111.g;
154
+            c.b = (1-d.r) * c000.b + (d.r-d.g) * c100.b + (d.g-d.b) * c110.b + (d.b) * c111.b;
155
+        } else if (d.r > d.b) {
156
+            c.r = (1-d.r) * c000.r + (d.r-d.b) * c100.r + (d.b-d.g) * c101.r + (d.g) * c111.r;
157
+            c.g = (1-d.r) * c000.g + (d.r-d.b) * c100.g + (d.b-d.g) * c101.g + (d.g) * c111.g;
158
+            c.b = (1-d.r) * c000.b + (d.r-d.b) * c100.b + (d.b-d.g) * c101.b + (d.g) * c111.b;
159
+        } else {
160
+            c.r = (1-d.b) * c000.r + (d.b-d.r) * c001.r + (d.r-d.g) * c101.r + (d.g) * c111.r;
161
+            c.g = (1-d.b) * c000.g + (d.b-d.r) * c001.g + (d.r-d.g) * c101.g + (d.g) * c111.g;
162
+            c.b = (1-d.b) * c000.b + (d.b-d.r) * c001.b + (d.r-d.g) * c101.b + (d.g) * c111.b;
163
+        }
164
+    } else {
165
+        if (d.b > d.g) {
166
+            c.r = (1-d.b) * c000.r + (d.b-d.g) * c001.r + (d.g-d.r) * c011.r + (d.r) * c111.r;
167
+            c.g = (1-d.b) * c000.g + (d.b-d.g) * c001.g + (d.g-d.r) * c011.g + (d.r) * c111.g;
168
+            c.b = (1-d.b) * c000.b + (d.b-d.g) * c001.b + (d.g-d.r) * c011.b + (d.r) * c111.b;
169
+        } else if (d.b > d.r) {
170
+            c.r = (1-d.g) * c000.r + (d.g-d.b) * c010.r + (d.b-d.r) * c011.r + (d.r) * c111.r;
171
+            c.g = (1-d.g) * c000.g + (d.g-d.b) * c010.g + (d.b-d.r) * c011.g + (d.r) * c111.g;
172
+            c.b = (1-d.g) * c000.b + (d.g-d.b) * c010.b + (d.b-d.r) * c011.b + (d.r) * c111.b;
173
+        } else {
174
+            c.r = (1-d.g) * c000.r + (d.g-d.r) * c010.r + (d.r-d.b) * c110.r + (d.b) * c111.r;
175
+            c.g = (1-d.g) * c000.g + (d.g-d.r) * c010.g + (d.r-d.b) * c110.g + (d.b) * c111.g;
176
+            c.b = (1-d.g) * c000.b + (d.g-d.r) * c010.b + (d.r-d.b) * c110.b + (d.b) * c111.b;
177
+        }
178
+    }
179
+    return c;
180
+}
181
+
182
+#define DEFINE_INTERP_FUNC(name, nbits)                                     \
183
+static struct rgbvec interp_##nbits##_##name(const LUT3DContext *lut3d,     \
184
+                                             uint##nbits##_t r,             \
185
+                                             uint##nbits##_t g,             \
186
+                                             uint##nbits##_t b)             \
187
+{                                                                           \
188
+    const float scale = (1. / ((1<<nbits) - 1)) * (lut3d->lutsize - 1);     \
189
+    const struct rgbvec scaled_rgb = {r * scale, g * scale, b * scale};     \
190
+    return interp_##name(lut3d, &scaled_rgb);                               \
191
+}
192
+
193
+DEFINE_INTERP_FUNC(nearest,     8)
194
+DEFINE_INTERP_FUNC(trilinear,   8)
195
+DEFINE_INTERP_FUNC(tetrahedral, 8)
196
+
197
+DEFINE_INTERP_FUNC(nearest,     16)
198
+DEFINE_INTERP_FUNC(trilinear,   16)
199
+DEFINE_INTERP_FUNC(tetrahedral, 16)
200
+
201
+#define MAX_LINE_SIZE 512
202
+
203
+static int skip_line(const char *p)
204
+{
205
+    while (*p && av_isspace(*p))
206
+        p++;
207
+    return !*p || *p == '#';
208
+}
209
+
210
+#define NEXT_LINE(loop_cond) do {                           \
211
+    if (!fgets(line, sizeof(line), f)) {                    \
212
+        av_log(ctx, AV_LOG_ERROR, "Unexpected EOF\n");      \
213
+        return AVERROR_INVALIDDATA;                         \
214
+    }                                                       \
215
+} while (loop_cond)
216
+
217
+/* Basically r g and b float values on each line; seems to be generated by
218
+ * Davinci */
219
+static int parse_dat(AVFilterContext *ctx, FILE *f)
220
+{
221
+    LUT3DContext *lut3d = ctx->priv;
222
+    const int size = lut3d->lutsize;
223
+    int i, j, k;
224
+
225
+    for (k = 0; k < size; k++) {
226
+        for (j = 0; j < size; j++) {
227
+            for (i = 0; i < size; i++) {
228
+                char line[MAX_LINE_SIZE];
229
+                struct rgbvec *vec = &lut3d->lut[k][j][i];
230
+                NEXT_LINE(skip_line(line));
231
+                sscanf(line, "%f %f %f", &vec->r, &vec->g, &vec->b);
232
+            }
233
+        }
234
+    }
235
+    return 0;
236
+}
237
+
238
+/* Iridas format */
239
+static int parse_cube(AVFilterContext *ctx, FILE *f)
240
+{
241
+    LUT3DContext *lut3d = ctx->priv;
242
+    char line[MAX_LINE_SIZE];
243
+    float min[3] = {0.0, 0.0, 0.0};
244
+    float max[3] = {1.0, 1.0, 1.0};
245
+
246
+    while (fgets(line, sizeof(line), f)) {
247
+        if (!strncmp(line, "LUT_3D_SIZE ", 12)) {
248
+            int i, j, k;
249
+            const int size = strtol(line + 12, NULL, 0);
250
+
251
+            if (size > MAX_LEVEL) {
252
+                av_log(ctx, AV_LOG_ERROR, "Too large 3D LUT\n");
253
+                return AVERROR(EINVAL);
254
+            }
255
+            lut3d->lutsize = size;
256
+            for (k = 0; k < size; k++) {
257
+                for (j = 0; j < size; j++) {
258
+                    for (i = 0; i < size; i++) {
259
+                        struct rgbvec *vec = &lut3d->lut[k][j][i];
260
+
261
+                        do {
262
+                            NEXT_LINE(0);
263
+                            if (!strncmp(line, "DOMAIN_", 7)) {
264
+                                float *vals = NULL;
265
+                                if      (!strncmp(line + 7, "MIN ", 4)) vals = min;
266
+                                else if (!strncmp(line + 7, "MAX ", 4)) vals = max;
267
+                                if (!vals)
268
+                                    return AVERROR_INVALIDDATA;
269
+                                sscanf(line + 11, "%f %f %f", vals, vals + 1, vals + 2);
270
+                                av_log(ctx, AV_LOG_DEBUG, "min: %f %f %f | max: %f %f %f\n",
271
+                                       min[0], min[1], min[2], max[0], max[1], max[2]);
272
+                                continue;
273
+                            }
274
+                        } while (skip_line(line));
275
+                        if (sscanf(line, "%f %f %f", &vec->r, &vec->g, &vec->b) != 3)
276
+                            return AVERROR_INVALIDDATA;
277
+                        vec->r *= max[0] - min[0];
278
+                        vec->g *= max[1] - min[1];
279
+                        vec->b *= max[2] - min[2];
280
+                    }
281
+                }
282
+            }
283
+            break;
284
+        }
285
+    }
286
+    return 0;
287
+}
288
+
289
+/* Assume 17x17x17 LUT with a 16-bit depth
290
+ * FIXME: it seems there are various 3dl formats */
291
+static int parse_3dl(AVFilterContext *ctx, FILE *f)
292
+{
293
+    char line[MAX_LINE_SIZE];
294
+    LUT3DContext *lut3d = ctx->priv;
295
+    int i, j, k;
296
+    const int size = 17;
297
+    const float scale = 16*16*16;
298
+
299
+    lut3d->lutsize = size;
300
+    if (!fgets(line, sizeof(line), f))
301
+        return AVERROR_INVALIDDATA;
302
+    for (k = 0; k < size; k++) {
303
+        for (j = 0; j < size; j++) {
304
+            for (i = 0; i < size; i++) {
305
+                int r, g, b;
306
+                struct rgbvec *vec = &lut3d->lut[k][j][i];
307
+
308
+                NEXT_LINE(skip_line(line));
309
+                if (sscanf(line, "%d %d %d", &r, &g, &b) != 3)
310
+                    return AVERROR_INVALIDDATA;
311
+                vec->r = r / scale;
312
+                vec->g = g / scale;
313
+                vec->b = b / scale;
314
+            }
315
+        }
316
+    }
317
+    return 0;
318
+}
319
+
320
+/* Pandora format */
321
+static int parse_m3d(AVFilterContext *ctx, FILE *f)
322
+{
323
+    LUT3DContext *lut3d = ctx->priv;
324
+    float scale;
325
+    int i, j, k, size, in = -1, out = -1;
326
+    char line[MAX_LINE_SIZE];
327
+    uint8_t rgb_map[3] = {0, 1, 2};
328
+
329
+    while (fgets(line, sizeof(line), f)) {
330
+        if      (!strncmp(line, "in",  2)) in  = strtol(line + 2, NULL, 0);
331
+        else if (!strncmp(line, "out", 3)) out = strtol(line + 3, NULL, 0);
332
+        else if (!strncmp(line, "values", 6)) {
333
+            const char *p = line + 6;
334
+#define SET_COLOR(id) do {                  \
335
+    while (av_isspace(*p))                  \
336
+        p++;                                \
337
+    switch (*p) {                           \
338
+    case 'r': rgb_map[id] = 0; break;       \
339
+    case 'g': rgb_map[id] = 1; break;       \
340
+    case 'b': rgb_map[id] = 2; break;       \
341
+    }                                       \
342
+    while (*p && !av_isspace(*p))           \
343
+        p++;                                \
344
+} while (0)
345
+            SET_COLOR(0);
346
+            SET_COLOR(1);
347
+            SET_COLOR(2);
348
+            break;
349
+        }
350
+    }
351
+
352
+    if (in == -1 || out == -1) {
353
+        av_log(ctx, AV_LOG_ERROR, "in and out must be defined\n");
354
+        return AVERROR_INVALIDDATA;
355
+    }
356
+    for (size = 1; size*size*size < in; size++);
357
+    lut3d->lutsize = size;
358
+    scale = 1. / (out - 1);
359
+
360
+    for (k = 0; k < size; k++) {
361
+        for (j = 0; j < size; j++) {
362
+            for (i = 0; i < size; i++) {
363
+                struct rgbvec *vec = &lut3d->lut[k][j][i];
364
+                float val[3];
365
+
366
+                NEXT_LINE(0);
367
+                if (sscanf(line, "%f %f %f", val, val + 1, val + 2) != 3)
368
+                    return AVERROR_INVALIDDATA;
369
+                vec->r = val[rgb_map[0]] * scale;
370
+                vec->g = val[rgb_map[1]] * scale;
371
+                vec->b = val[rgb_map[2]] * scale;
372
+            }
373
+        }
374
+    }
375
+    return 0;
376
+}
377
+
378
+static void set_identity_matrix(LUT3DContext *lut3d, int size)
379
+{
380
+    int i, j, k;
381
+    const float c = 1. / (size - 1);
382
+
383
+    lut3d->lutsize = size;
384
+    for (k = 0; k < size; k++) {
385
+        for (j = 0; j < size; j++) {
386
+            for (i = 0; i < size; i++) {
387
+                struct rgbvec *vec = &lut3d->lut[k][j][i];
388
+                vec->r = k * c;
389
+                vec->g = j * c;
390
+                vec->b = i * c;
391
+            }
392
+        }
393
+    }
394
+}
395
+
396
+static av_cold int init(AVFilterContext *ctx)
397
+{
398
+    int ret;
399
+    FILE *f;
400
+    const char *ext;
401
+    LUT3DContext *lut3d = ctx->priv;
402
+
403
+    if (!lut3d->file) {
404
+        set_identity_matrix(lut3d, 32);
405
+        return 0;
406
+    }
407
+
408
+    f = fopen(lut3d->file, "r");
409
+    if (!f) {
410
+        ret = AVERROR(errno);
411
+        av_log(ctx, AV_LOG_ERROR, "%s: %s\n", lut3d->file, av_err2str(ret));
412
+        return ret;
413
+    }
414
+
415
+    ext = strrchr(lut3d->file, '.');
416
+    if (!ext) {
417
+        av_log(ctx, AV_LOG_ERROR, "Unable to guess the format from the extension\n");
418
+        ret = AVERROR_INVALIDDATA;
419
+        goto end;
420
+    }
421
+    ext++;
422
+
423
+    if (!av_strcasecmp(ext, "dat")) {
424
+        lut3d->lutsize = 33;
425
+        ret = parse_dat(ctx, f);
426
+    } else if (!av_strcasecmp(ext, "3dl")) {
427
+        ret = parse_3dl(ctx, f);
428
+    } else if (!av_strcasecmp(ext, "cube")) {
429
+        ret = parse_cube(ctx, f);
430
+    } else if (!av_strcasecmp(ext, "m3d")) {
431
+        ret = parse_m3d(ctx, f);
432
+    } else {
433
+        av_log(ctx, AV_LOG_ERROR, "Unrecognized '.%s' file type\n", ext);
434
+        ret = AVERROR(EINVAL);
435
+    }
436
+
437
+    if (!ret && !lut3d->lutsize) {
438
+        av_log(ctx, AV_LOG_ERROR, "3D LUT is empty\n");
439
+        ret = AVERROR_INVALIDDATA;
440
+    }
441
+
442
+end:
443
+    fclose(f);
444
+    return ret;
445
+}
446
+
447
+static int query_formats(AVFilterContext *ctx)
448
+{
449
+    static const enum AVPixelFormat pix_fmts[] = {
450
+        AV_PIX_FMT_RGB24,  AV_PIX_FMT_BGR24,
451
+        AV_PIX_FMT_RGBA,   AV_PIX_FMT_BGRA,
452
+        AV_PIX_FMT_ARGB,   AV_PIX_FMT_ABGR,
453
+        AV_PIX_FMT_0RGB,   AV_PIX_FMT_0BGR,
454
+        AV_PIX_FMT_RGB0,   AV_PIX_FMT_BGR0,
455
+        AV_PIX_FMT_RGB48,  AV_PIX_FMT_BGR48,
456
+        AV_PIX_FMT_RGBA64, AV_PIX_FMT_BGRA64,
457
+        AV_PIX_FMT_NONE
458
+    };
459
+    ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
460
+    return 0;
461
+}
462
+
463
+static int config_input(AVFilterLink *inlink)
464
+{
465
+    LUT3DContext *lut3d = inlink->dst->priv;
466
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
467
+
468
+    switch (inlink->format) {
469
+    case AV_PIX_FMT_RGB48:
470
+    case AV_PIX_FMT_BGR48:
471
+    case AV_PIX_FMT_RGBA64:
472
+    case AV_PIX_FMT_BGRA64:
473
+        lut3d->is16bit = 1;
474
+    }
475
+
476
+    ff_fill_rgba_map(lut3d->rgba_map, inlink->format);
477
+    lut3d->step = av_get_padded_bits_per_pixel(desc) >> (3 + lut3d->is16bit);
478
+
479
+#define SET_FUNC(name) do {                                     \
480
+    if (lut3d->is16bit) lut3d->interp_16 = interp_16_##name;    \
481
+    else                lut3d->interp_8  = interp_8_##name;     \
482
+} while (0)
483
+
484
+    switch (lut3d->interpolation) {
485
+    case INTERPOLATE_NEAREST:     SET_FUNC(nearest);        break;
486
+    case INTERPOLATE_TRILINEAR:   SET_FUNC(trilinear);      break;
487
+    case INTERPOLATE_TETRAHEDRAL: SET_FUNC(tetrahedral);    break;
488
+    default:
489
+        av_assert0(0);
490
+    }
491
+
492
+    return 0;
493
+}
494
+
495
+#define FILTER(nbits) do {                                                                          \
496
+    uint8_t       *dstrow = out->data[0];                                                           \
497
+    const uint8_t *srcrow = in ->data[0];                                                           \
498
+                                                                                                    \
499
+    for (y = 0; y < inlink->h; y++) {                                                               \
500
+        uint##nbits##_t *dst = (uint##nbits##_t *)dstrow;                                           \
501
+        const uint##nbits##_t *src = (const uint##nbits##_t *)srcrow;                               \
502
+        for (x = 0; x < inlink->w * step; x += step) {                                              \
503
+            struct rgbvec vec = lut3d->interp_##nbits(lut3d, src[x + r], src[x + g], src[x + b]);   \
504
+            dst[x + r] = av_clip_uint##nbits(vec.r * (float)((1<<nbits) - 1));                      \
505
+            dst[x + g] = av_clip_uint##nbits(vec.g * (float)((1<<nbits) - 1));                      \
506
+            dst[x + b] = av_clip_uint##nbits(vec.b * (float)((1<<nbits) - 1));                      \
507
+            if (!direct && step == 4)                                                               \
508
+                dst[x + a] = src[x + a];                                                            \
509
+        }                                                                                           \
510
+        dstrow += out->linesize[0];                                                                 \
511
+        srcrow += in ->linesize[0];                                                                 \
512
+    }                                                                                               \
513
+} while (0)
514
+
515
+static int filter_frame(AVFilterLink *inlink, AVFrame *in)
516
+{
517
+    int x, y, direct = 0;
518
+    AVFilterContext *ctx = inlink->dst;
519
+    LUT3DContext *lut3d = ctx->priv;
520
+    AVFilterLink *outlink = inlink->dst->outputs[0];
521
+    AVFrame *out;
522
+    const int step = lut3d->step;
523
+    const uint8_t r = lut3d->rgba_map[R];
524
+    const uint8_t g = lut3d->rgba_map[G];
525
+    const uint8_t b = lut3d->rgba_map[B];
526
+    const uint8_t a = lut3d->rgba_map[A];
527
+
528
+    if (av_frame_is_writable(in)) {
529
+        direct = 1;
530
+        out = in;
531
+    } else {
532
+        out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
533
+        if (!out) {
534
+            av_frame_free(&in);
535
+            return AVERROR(ENOMEM);
536
+        }
537
+        av_frame_copy_props(out, in);
538
+    }
539
+
540
+    if (lut3d->is16bit) FILTER(16);
541
+    else                FILTER(8);
542
+
543
+    if (!direct)
544
+        av_frame_free(&in);
545
+
546
+    return ff_filter_frame(outlink, out);
547
+}
548
+
549
+static const AVFilterPad lut3d_inputs[] = {
550
+    {
551
+        .name         = "default",
552
+        .type         = AVMEDIA_TYPE_VIDEO,
553
+        .filter_frame = filter_frame,
554
+        .config_props = config_input,
555
+    },
556
+    { NULL }
557
+};
558
+
559
+static const AVFilterPad lut3d_outputs[] = {
560
+     {
561
+         .name = "default",
562
+         .type = AVMEDIA_TYPE_VIDEO,
563
+     },
564
+     { NULL }
565
+};
566
+
567
+AVFilter avfilter_vf_lut3d = {
568
+    .name          = "lut3d",
569
+    .description   = NULL_IF_CONFIG_SMALL("Adjust colors using a 3D LUT."),
570
+    .priv_size     = sizeof(LUT3DContext),
571
+    .init          = init,
572
+    .query_formats = query_formats,
573
+    .inputs        = lut3d_inputs,
574
+    .outputs       = lut3d_outputs,
575
+    .priv_class    = &lut3d_class,
576
+    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
577
+};