Browse code

lavfi: add vignette filter.

Clément Bœsch authored on 2013/04/02 19:48:32
Showing 6 changed files
... ...
@@ -59,6 +59,7 @@ version <next>:
59 59
 - VC-1 interlaced B-frame support
60 60
 - support for WavPack muxing (raw and in Matroska)
61 61
 - XVideo output device
62
+- vignette filter
62 63
 
63 64
 
64 65
 version 1.2:
... ...
@@ -6950,6 +6950,109 @@ For example, to vertically flip a video with @command{ffmpeg}:
6950 6950
 ffmpeg -i in.avi -vf "vflip" out.avi
6951 6951
 @end example
6952 6952
 
6953
+@section vignette
6954
+
6955
+Make or reverse a natural vignetting effect.
6956
+
6957
+The filter accepts the following options:
6958
+
6959
+@table @option
6960
+@item angle, a
6961
+Set lens angle expression as a number of radians.
6962
+
6963
+The value is clipped in the @code{[0,PI/2]} range.
6964
+
6965
+Default value: @code{"PI/5"}
6966
+
6967
+@item x0
6968
+@item y0
6969
+Set center coordinates expressions. Respectively @code{"w/2"} and @code{"h/2"}
6970
+by default.
6971
+
6972
+@item mode
6973
+Set forward/backward mode.
6974
+
6975
+Available modes are:
6976
+@table @samp
6977
+@item forward
6978
+The larger the distance from the central point, the darker the image becomes.
6979
+
6980
+@item backward
6981
+The larger the distance from the central point, the brighter the image becomes.
6982
+This can be used to reverse a vignette effect, though there is no automatic
6983
+detection to extract the lens @option{angle} and other settings (yet). It can
6984
+also be used to create a burning effect.
6985
+@end table
6986
+
6987
+Default value is @samp{forward}.
6988
+
6989
+@item eval
6990
+Set evaluation mode for the expressions (@option{angle}, @option{x0}, @option{y0}).
6991
+
6992
+It accepts the following values:
6993
+@table @samp
6994
+@item init
6995
+Evaluate expressions only once during the filter initialization.
6996
+
6997
+@item frame
6998
+Evaluate expressions for each incoming frame. This is way slower than the
6999
+@samp{init} mode since it requires all the scalers to be re-computed, but it
7000
+allows advanced dynamic expressions.
7001
+@end table
7002
+
7003
+Default value is @samp{init}.
7004
+
7005
+@item dither
7006
+Set dithering to reduce the circular banding effects. Default is @code{1}
7007
+(enabled).
7008
+@end table
7009
+
7010
+@subsection Expressions
7011
+
7012
+The @option{alpha}, @option{x0} and @option{y0} expressions can contain the
7013
+following parameters.
7014
+
7015
+@table @option
7016
+@item w
7017
+@item h
7018
+input width and height
7019
+
7020
+@item n
7021
+the number of input frame, starting from 0
7022
+
7023
+@item pts
7024
+the PTS (Presentation TimeStamp) time of the filtered video frame, expressed in
7025
+@var{TB} units, NAN if undefined
7026
+
7027
+@item r
7028
+frame rate of the input video, NAN if the input frame rate is unknown
7029
+
7030
+@item t
7031
+the PTS (Presentation TimeStamp) of the filtered video frame,
7032
+expressed in seconds, NAN if undefined
7033
+
7034
+@item tb
7035
+time base of the input video
7036
+@end table
7037
+
7038
+
7039
+@subsection Examples
7040
+
7041
+@itemize
7042
+@item
7043
+Apply simple strong vignetting effect:
7044
+@example
7045
+vignette=PI/4
7046
+@end example
7047
+
7048
+@item
7049
+Make a flickering vignetting:
7050
+@example
7051
+vignette='PI/4+random(1)*PI/50':eval=frame
7052
+@end example
7053
+
7054
+@end itemize
7055
+
6953 7056
 @anchor{yadif}
6954 7057
 @section yadif
6955 7058
 
... ...
@@ -191,6 +191,7 @@ OBJS-$(CONFIG_UNSHARP_FILTER)                += vf_unsharp.o
191 191
 OBJS-$(CONFIG_VFLIP_FILTER)                  += vf_vflip.o
192 192
 OBJS-$(CONFIG_VIDSTABDETECT_FILTER)          += vidstabutils.o vf_vidstabdetect.o
193 193
 OBJS-$(CONFIG_VIDSTABTRANSFORM_FILTER)       += vidstabutils.o vf_vidstabtransform.o
194
+OBJS-$(CONFIG_VIGNETTE_FILTER)               += vf_vignette.o
194 195
 OBJS-$(CONFIG_YADIF_FILTER)                  += vf_yadif.o
195 196
 OBJS-$(CONFIG_ZMQ_FILTER)                    += f_zmq.o
196 197
 
... ...
@@ -188,6 +188,7 @@ void avfilter_register_all(void)
188 188
     REGISTER_FILTER(VFLIP,          vflip,          vf);
189 189
     REGISTER_FILTER(VIDSTABDETECT,  vidstabdetect,  vf);
190 190
     REGISTER_FILTER(VIDSTABTRANSFORM, vidstabtransform, vf);
191
+    REGISTER_FILTER(VIGNETTE,       vignette,       vf);
191 192
     REGISTER_FILTER(YADIF,          yadif,          vf);
192 193
     REGISTER_FILTER(ZMQ,            zmq,            vf);
193 194
 
... ...
@@ -30,7 +30,7 @@
30 30
 #include "libavutil/avutil.h"
31 31
 
32 32
 #define LIBAVFILTER_VERSION_MAJOR  3
33
-#define LIBAVFILTER_VERSION_MINOR  72
33
+#define LIBAVFILTER_VERSION_MINOR  73
34 34
 #define LIBAVFILTER_VERSION_MICRO 100
35 35
 
36 36
 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
37 37
new file mode 100644
... ...
@@ -0,0 +1,330 @@
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
+#include "libavutil/opt.h"
21
+#include "libavutil/eval.h"
22
+#include "libavutil/avassert.h"
23
+#include "libavutil/pixdesc.h"
24
+#include "avfilter.h"
25
+#include "formats.h"
26
+#include "internal.h"
27
+#include "video.h"
28
+
29
+static const char *const var_names[] = {
30
+    "w",    // stream width
31
+    "h",    // stream height
32
+    "n",    // frame count
33
+    "pts",  // presentation timestamp expressed in AV_TIME_BASE units
34
+    "r",    // frame rate
35
+    "t",    // timestamp expressed in seconds
36
+    "tb",   // timebase
37
+    NULL
38
+};
39
+
40
+enum var_name {
41
+    VAR_W,
42
+    VAR_H,
43
+    VAR_N,
44
+    VAR_PTS,
45
+    VAR_R,
46
+    VAR_T,
47
+    VAR_TB,
48
+    VAR_NB
49
+};
50
+
51
+typedef struct {
52
+    const AVClass *class;
53
+    const AVPixFmtDescriptor *desc;
54
+    int backward;
55
+    enum EvalMode { EVAL_MODE_INIT, EVAL_MODE_FRAME, EVAL_MODE_NB } eval_mode;
56
+#define DEF_EXPR_FIELDS(name) AVExpr *name##_pexpr; char *name##_expr; double name;
57
+    DEF_EXPR_FIELDS(angle);
58
+    DEF_EXPR_FIELDS(x0);
59
+    DEF_EXPR_FIELDS(y0);
60
+    double var_values[VAR_NB];
61
+    float *fmap;
62
+    int fmap_linesize;
63
+    double dmax;
64
+    float xscale, yscale;
65
+    uint32_t dither;
66
+    int do_dither;
67
+} VignetteContext;
68
+
69
+#define OFFSET(x) offsetof(VignetteContext, x)
70
+#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
71
+static const AVOption vignette_options[] = {
72
+    { "angle", "set lens angle", OFFSET(angle_expr), AV_OPT_TYPE_STRING, {.str="PI/5"}, .flags = FLAGS },
73
+    { "a",     "set lens angle", OFFSET(angle_expr), AV_OPT_TYPE_STRING, {.str="PI/5"}, .flags = FLAGS },
74
+    { "x0", "set circle center position on x-axis", OFFSET(x0_expr), AV_OPT_TYPE_STRING, {.str="w/2"}, .flags = FLAGS },
75
+    { "y0", "set circle center position on y-axis", OFFSET(y0_expr), AV_OPT_TYPE_STRING, {.str="h/2"}, .flags = FLAGS },
76
+    { "mode", "set forward/backward mode", OFFSET(backward), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, FLAGS, "mode" },
77
+        { "forward",  NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 0}, INT_MIN, INT_MAX, FLAGS, "mode"},
78
+        { "backward", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 1}, INT_MIN, INT_MAX, FLAGS, "mode"},
79
+    { "eval", "specify when to evaluate expressions", OFFSET(eval_mode), AV_OPT_TYPE_INT, {.i64 = EVAL_MODE_INIT}, 0, EVAL_MODE_NB-1, FLAGS, "eval" },
80
+         { "init",  "eval expressions once during initialization", 0, AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_INIT},  .flags = FLAGS, .unit = "eval" },
81
+         { "frame", "eval expressions for each frame",             0, AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_FRAME}, .flags = FLAGS, .unit = "eval" },
82
+    { "dither", "set dithering", OFFSET(do_dither), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, FLAGS },
83
+    { NULL }
84
+};
85
+
86
+AVFILTER_DEFINE_CLASS(vignette);
87
+
88
+static av_cold int init(AVFilterContext *ctx)
89
+{
90
+    VignetteContext *s = ctx->priv;
91
+
92
+#define PARSE_EXPR(name) do {                                               \
93
+    int ret = av_expr_parse(&s->name##_pexpr,  s->name##_expr, var_names,   \
94
+                            NULL, NULL, NULL, NULL, 0, ctx);                \
95
+    if (ret < 0) {                                                          \
96
+        av_log(ctx, AV_LOG_ERROR, "Unable to parse expression for '"        \
97
+               AV_STRINGIFY(name) "'\n");                                   \
98
+        return ret;                                                         \
99
+    }                                                                       \
100
+} while (0)
101
+
102
+    PARSE_EXPR(angle);
103
+    PARSE_EXPR(x0);
104
+    PARSE_EXPR(y0);
105
+    return 0;
106
+}
107
+
108
+static av_cold void uninit(AVFilterContext *ctx)
109
+{
110
+    VignetteContext *s = ctx->priv;
111
+    av_freep(&s->fmap);
112
+    av_expr_free(s->angle_pexpr);
113
+    av_expr_free(s->x0_pexpr);
114
+    av_expr_free(s->y0_pexpr);
115
+}
116
+
117
+static int query_formats(AVFilterContext *ctx)
118
+{
119
+    static const enum AVPixelFormat pix_fmts[] = {
120
+        AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV422P,
121
+        AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV411P,
122
+        AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV440P,
123
+        AV_PIX_FMT_RGB24,   AV_PIX_FMT_BGR24,
124
+        AV_PIX_FMT_GRAY8,
125
+        AV_PIX_FMT_NONE
126
+    };
127
+    ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
128
+    return 0;
129
+}
130
+
131
+static double get_natural_factor(const VignetteContext *s, int x, int y)
132
+{
133
+    const int xx = (x - s->x0) * s->xscale;
134
+    const int yy = (y - s->y0) * s->yscale;
135
+    const double dnorm = hypot(xx, yy) / s->dmax;
136
+    if (dnorm > 1) {
137
+        return 0;
138
+    } else {
139
+        const double c = cos(s->angle * dnorm);
140
+        return (c*c)*(c*c); // do not remove braces, it helps compilers
141
+    }
142
+}
143
+
144
+#define TS2D(ts)     ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts))
145
+#define TS2T(ts, tb) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts) * av_q2d(tb))
146
+
147
+static void update_context(VignetteContext *s, AVFilterLink *inlink, AVFrame *frame)
148
+{
149
+    int x, y;
150
+    float *dst = s->fmap;
151
+    int dst_linesize = s->fmap_linesize;
152
+
153
+    if (frame) {
154
+        s->var_values[VAR_N]   = inlink->frame_count;
155
+        s->var_values[VAR_T]   = TS2T(frame->pts, inlink->time_base);
156
+        s->var_values[VAR_PTS] = TS2D(frame->pts);
157
+    } else {
158
+        s->var_values[VAR_N]   = 0;
159
+        s->var_values[VAR_T]   = NAN;
160
+        s->var_values[VAR_PTS] = NAN;
161
+    }
162
+
163
+    s->angle = av_clipf(av_expr_eval(s->angle_pexpr, s->var_values, NULL), 0, M_PI_2);
164
+    s->x0 = av_expr_eval(s->x0_pexpr, s->var_values, NULL);
165
+    s->y0 = av_expr_eval(s->y0_pexpr, s->var_values, NULL);
166
+
167
+    if (s->backward) {
168
+        for (y = 0; y < inlink->h; y++) {
169
+            for (x = 0; x < inlink->w; x++)
170
+                dst[x] = 1. / get_natural_factor(s, x, y);
171
+            dst += dst_linesize;
172
+        }
173
+    } else {
174
+        for (y = 0; y < inlink->h; y++) {
175
+            for (x = 0; x < inlink->w; x++)
176
+                dst[x] = get_natural_factor(s, x, y);
177
+            dst += dst_linesize;
178
+        }
179
+    }
180
+}
181
+
182
+static inline double get_dither_value(VignetteContext *s)
183
+{
184
+    double dv = 0;
185
+    if (s->do_dither) {
186
+        dv = s->dither / (double)(1LL<<32);
187
+        s->dither = s->dither * 1664525 + 1013904223;
188
+    }
189
+    return dv;
190
+}
191
+
192
+static int filter_frame(AVFilterLink *inlink, AVFrame *in)
193
+{
194
+    unsigned x, y;
195
+    AVFilterContext *ctx = inlink->dst;
196
+    VignetteContext *s = ctx->priv;
197
+    AVFilterLink *outlink = inlink->dst->outputs[0];
198
+    AVFrame *out;
199
+
200
+    out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
201
+    if (!out) {
202
+        av_frame_free(&in);
203
+        return AVERROR(ENOMEM);
204
+    }
205
+    av_frame_copy_props(out, in);
206
+
207
+    if (s->eval_mode == EVAL_MODE_FRAME)
208
+        update_context(s, inlink, in);
209
+
210
+    if (s->desc->flags & PIX_FMT_RGB) {
211
+        uint8_t       *dst = out->data[0];
212
+        const uint8_t *src = in ->data[0];
213
+        const float *fmap = s->fmap;
214
+        const int dst_linesize = out->linesize[0];
215
+        const int src_linesize = in ->linesize[0];
216
+        const int fmap_linesize = s->fmap_linesize;
217
+
218
+        for (y = 0; y < inlink->h; y++) {
219
+            uint8_t       *dstp = dst;
220
+            const uint8_t *srcp = src;
221
+
222
+            for (x = 0; x < inlink->w; x++, dstp += 3, srcp += 3) {
223
+                const float f = fmap[x];
224
+
225
+                dstp[0] = av_clip_uint8(srcp[0] * f + get_dither_value(s));
226
+                dstp[1] = av_clip_uint8(srcp[1] * f + get_dither_value(s));
227
+                dstp[2] = av_clip_uint8(srcp[2] * f + get_dither_value(s));
228
+            }
229
+            dst += dst_linesize;
230
+            src += src_linesize;
231
+            fmap += fmap_linesize;
232
+        }
233
+    } else {
234
+        int plane;
235
+
236
+        for (plane = 0; plane < 4 && in->data[plane]; plane++) {
237
+            uint8_t       *dst = out->data[plane];
238
+            const uint8_t *src = in ->data[plane];
239
+            const float *fmap = s->fmap;
240
+            const int dst_linesize = out->linesize[plane];
241
+            const int src_linesize = in ->linesize[plane];
242
+            const int fmap_linesize = s->fmap_linesize;
243
+            const int chroma = plane == 1 || plane == 2;
244
+            const int hsub = chroma ? s->desc->log2_chroma_w : 0;
245
+            const int vsub = chroma ? s->desc->log2_chroma_h : 0;
246
+            const int w = FF_CEIL_RSHIFT(inlink->w, hsub);
247
+            const int h = FF_CEIL_RSHIFT(inlink->h, vsub);
248
+
249
+            for (y = 0; y < h; y++) {
250
+                uint8_t *dstp = dst;
251
+                const uint8_t *srcp = src;
252
+
253
+                for (x = 0; x < w; x++) {
254
+                    const double dv = get_dither_value(s);
255
+                    if (chroma) *dstp++ = av_clip_uint8(fmap[x << hsub] * (*srcp++ - 127) + 127 + dv);
256
+                    else        *dstp++ = av_clip_uint8(fmap[x        ] *  *srcp++              + dv);
257
+                }
258
+                dst += dst_linesize;
259
+                src += src_linesize;
260
+                fmap += fmap_linesize << vsub;
261
+            }
262
+        }
263
+    }
264
+
265
+    return ff_filter_frame(outlink, out);
266
+}
267
+
268
+static int config_props(AVFilterLink *inlink)
269
+{
270
+    VignetteContext *s = inlink->dst->priv;
271
+
272
+    s->desc = av_pix_fmt_desc_get(inlink->format);
273
+    s->var_values[VAR_W]  = inlink->w;
274
+    s->var_values[VAR_H]  = inlink->h;
275
+    s->var_values[VAR_TB] = av_q2d(inlink->time_base);
276
+    s->var_values[VAR_R]  = inlink->frame_rate.num == 0 || inlink->frame_rate.den == 0 ?
277
+        NAN : av_q2d(inlink->frame_rate);
278
+
279
+    if (inlink->sample_aspect_ratio.num > inlink->sample_aspect_ratio.den) {
280
+        s->xscale = av_q2d(inlink->sample_aspect_ratio);
281
+        s->yscale = 1;
282
+        s->dmax = hypot(inlink->w / 2., s->yscale * inlink->h / 2.);
283
+    } else {
284
+        s->yscale = av_q2d(inlink->sample_aspect_ratio);
285
+        s->xscale = 1;
286
+        s->dmax = hypot(s->xscale * inlink->w / 2., inlink->h / 2.);
287
+    }
288
+
289
+    s->fmap_linesize = FFALIGN(inlink->w, 32);
290
+    s->fmap = av_malloc(s->fmap_linesize * inlink->h * sizeof(*s->fmap));
291
+    if (!s->fmap)
292
+        return AVERROR(ENOMEM);
293
+
294
+    if (s->eval_mode == EVAL_MODE_INIT)
295
+        update_context(s, inlink, NULL);
296
+
297
+    return 0;
298
+}
299
+
300
+static const AVFilterPad vignette_inputs[] = {
301
+    {
302
+        .name         = "default",
303
+        .type         = AVMEDIA_TYPE_VIDEO,
304
+        .filter_frame = filter_frame,
305
+        .config_props = config_props,
306
+    },
307
+    { NULL }
308
+};
309
+
310
+static const AVFilterPad vignette_outputs[] = {
311
+     {
312
+         .name = "default",
313
+         .type = AVMEDIA_TYPE_VIDEO,
314
+     },
315
+     { NULL }
316
+};
317
+
318
+AVFilter avfilter_vf_vignette = {
319
+    .name          = "vignette",
320
+    .description   = NULL_IF_CONFIG_SMALL("Make or reverse a vignette effect."),
321
+    .priv_size     = sizeof(VignetteContext),
322
+    .init          = init,
323
+    .uninit        = uninit,
324
+    .query_formats = query_formats,
325
+    .inputs        = vignette_inputs,
326
+    .outputs       = vignette_outputs,
327
+    .priv_class    = &vignette_class,
328
+    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
329
+};