[FFmpeg-devel] [PATCH] lavfi/WIP: vignette filter.

Clément Bœsch ubitux at gmail.com
Thu Apr 4 17:15:29 CEST 2013


---
An ugly encode showing a bit the feature:
  http://lucy.pkh.me/gonenutty-vintage-vignette.webm

The filtergraph used here was something like this (I don't remember the exact
settings:
  curves=vintage,vignette=edge_dist_factor=3:r1=(0.02*random(1)+0.79)*h/2:r2=(0.02*random(1)+0.79)*w/2

Note that it's still far from perfect. I have an annoying halo problem, which
can be seen in bright scenes: http://imgur.com/2wyhQlR

This bright halo in the center is actually where the picture starts to get
darker linearly (it's the ellipse defined by r1, r2).  Unfortunately, the eye
notices it fairly well when it's bright. I don't really know how to solve that.
One idea I had was to start darkening starting at the origin instead of the
outside of an ellipse, with an exponential or something.  Maybe some dithering
could also help. Any suggestion?
---
 doc/filters.texi          |  50 +++++++++
 libavfilter/Makefile      |   1 +
 libavfilter/allfilters.c  |   1 +
 libavfilter/vf_vignette.c | 273 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 325 insertions(+)
 create mode 100644 libavfilter/vf_vignette.c

diff --git a/doc/filters.texi b/doc/filters.texi
index 75e8a72..aaf1548 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -5582,6 +5582,56 @@ Flip the input video vertically.
 ffmpeg -i in.avi -vf "vflip" out.avi
 @end example
 
+ at section vignette
+
+Make a "vignette" effect on the video by adding shadows in the corners.
+
+The filter accepts parameters as a list of @var{key}=@var{value}
+pairs, separated by ":". If the key of the first options is omitted,
+the arguments are interpreted according to syntax
+ at var{r1}:@var{r2}:@var{x0}:@var{y0}.
+
+The description of the accepted parameters follows.
+
+ at table @option
+ at item r1, r2
+Set vertical and horizontal radius expressions of the "bright" ellipse. Outside
+the ellipse, the brightness starts to decrease. Default are respectively
+ at code{0.9*h/2} and @code{0.9*w/2}.
+
+ at item x0, y0
+Set the coordinates of the center of the ellipse. Default are respectively
+ at code{w/2} and @code{h/2}.
+
+ at item edge_dist_factor
+Set the distance factor between the edges and the ellipse. This allows to
+control the strength of the darkness on the edges. A high value will make the
+borders darker quickly while a low value will make it smoother. Default is @code{2}.
+ at end table
+
+The @var{r1}, @var{r2}, @var{x0} and @var{x0} parameters are expressions
+containing the following constants:
+
+ at table @option
+ at item w, h
+video width and height
+
+ at item n
+frame count of the input frame starting from 0
+
+ at item pts
+presentation timestamp of the input frame expressed in time base units
+
+ at item r
+frame rate of the input video, NAN if the input frame rate is unknown
+
+ at item t
+timestamp expressed in seconds, NAN if the input timestamp is unknown
+
+ at item tb
+time base of the input video
+ at end table
+
 @section yadif
 
 Deinterlace the input video ("yadif" means "yet another deinterlacing
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 277ff71..5451e68 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -169,6 +169,7 @@ OBJS-$(CONFIG_TINTERLACE_FILTER)             += vf_tinterlace.o
 OBJS-$(CONFIG_TRANSPOSE_FILTER)              += vf_transpose.o
 OBJS-$(CONFIG_UNSHARP_FILTER)                += vf_unsharp.o
 OBJS-$(CONFIG_VFLIP_FILTER)                  += vf_vflip.o
+OBJS-$(CONFIG_VIGNETTE_FILTER)               += vf_vignette.o
 OBJS-$(CONFIG_YADIF_FILTER)                  += vf_yadif.o
 
 OBJS-$(CONFIG_CELLAUTO_FILTER)               += vsrc_cellauto.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 4972322..53f740b 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -164,6 +164,7 @@ void avfilter_register_all(void)
     REGISTER_FILTER(TRANSPOSE,      transpose,      vf);
     REGISTER_FILTER(UNSHARP,        unsharp,        vf);
     REGISTER_FILTER(VFLIP,          vflip,          vf);
+    REGISTER_FILTER(VIGNETTE,       vignette,       vf);
     REGISTER_FILTER(YADIF,          yadif,          vf);
 
     REGISTER_FILTER(CELLAUTO,       cellauto,       vsrc);
diff --git a/libavfilter/vf_vignette.c b/libavfilter/vf_vignette.c
new file mode 100644
index 0000000..743b194
--- /dev/null
+++ b/libavfilter/vf_vignette.c
@@ -0,0 +1,273 @@
+/*
+ * Copyright (c) 2013 Clément Bœsch
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libavutil/opt.h"
+#include "libavutil/eval.h"
+#include "libavutil/avassert.h"
+#include "libavutil/pixdesc.h"
+#include "avfilter.h"
+#include "formats.h"
+#include "internal.h"
+#include "video.h"
+
+static const char *const var_names[] = {
+    "w",    // stream width
+    "h",    // stream height
+    "n",    // frame count
+    "pts",  // presentation timestamp expressed in AV_TIME_BASE units
+    "r",    // frame rate
+    "t",    // timestamp expressed in seconds
+    "tb",   // timebase
+    NULL
+};
+
+enum var_name {
+    VAR_W,
+    VAR_H,
+    VAR_N,
+    VAR_PTS,
+    VAR_R,
+    VAR_T,
+    VAR_TB,
+    VAR_NB
+};
+
+typedef struct {
+    const AVClass *class;
+    const AVPixFmtDescriptor *desc;
+#define DEF_EXPR_FIELDS(name) AVExpr *name##_pexpr; char *name##_expr; int name
+    DEF_EXPR_FIELDS(r1);
+    DEF_EXPR_FIELDS(r2);
+    DEF_EXPR_FIELDS(x0);
+    DEF_EXPR_FIELDS(y0);
+    double edge_dist_factor;
+    double var_values[VAR_NB];
+} VignetteContext;
+
+#define OFFSET(x) offsetof(VignetteContext, x)
+#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
+static const AVOption vignette_options[] = {
+    { "r1", "set vertical radius of the ellipse",       OFFSET(r1_expr),     AV_OPT_TYPE_STRING, {.str="0.9*h/2"},  .flags = FLAGS },
+    { "r2", "set horizontal radius of the ellipse",     OFFSET(r2_expr),     AV_OPT_TYPE_STRING, {.str="0.9*w/2"},  .flags = FLAGS },
+    { "x0", "set ellipse center pos on x-axis",         OFFSET(x0_expr),     AV_OPT_TYPE_STRING, {.str="w/2"},      .flags = FLAGS },
+    { "y0", "set ellipse center pos on y-axis",         OFFSET(y0_expr),     AV_OPT_TYPE_STRING, {.str="h/2"},      .flags = FLAGS },
+    { "edge_dist_factor", "set distance factor between the edges and the ellipse", OFFSET(edge_dist_factor), AV_OPT_TYPE_DOUBLE, {.dbl=2}, 1, 1000, .flags = FLAGS },
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(vignette);
+
+static av_cold int init(AVFilterContext *ctx, const char *args)
+{
+    int ret;
+    VignetteContext *vignette = ctx->priv;
+
+    if ((ret = av_expr_parse(&vignette->r1_pexpr, vignette->r1_expr, var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0 ||
+        (ret = av_expr_parse(&vignette->r2_pexpr, vignette->r2_expr, var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0 ||
+        (ret = av_expr_parse(&vignette->x0_pexpr, vignette->x0_expr, var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0 ||
+        (ret = av_expr_parse(&vignette->y0_pexpr, vignette->y0_expr, var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0)
+        return ret;
+    return 0;
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    VignetteContext *vignette = ctx->priv;
+    av_expr_free(vignette->x0_pexpr);
+    av_expr_free(vignette->y0_pexpr);
+    av_expr_free(vignette->r1_pexpr);
+    av_expr_free(vignette->r2_pexpr);
+}
+
+static int query_formats(AVFilterContext *ctx)
+{
+    static const enum AVPixelFormat pix_fmts[] = {
+        AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV422P,
+        AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV411P,
+        AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV440P,
+        AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24,
+        AV_PIX_FMT_GRAY8,
+        AV_PIX_FMT_NONE
+    };
+    ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
+    return 0;
+}
+
+#define TS2D(ts) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts))
+#define TS2T(ts, tb) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts) * av_q2d(tb))
+
+static double get_brightness_factor(const VignetteContext *vignette, int x, int y)
+{
+    double d, r;
+    double xx = x - vignette->x0;
+    double yy = y - vignette->y0;
+
+    if (vignette->r1 > vignette->r2) {
+        xx *= (double)vignette->r1 / vignette->r2;
+        r = vignette->r2;
+    } else {
+        yy *= (double)vignette->r2 / vignette->r1;
+        r = vignette->r1;
+    }
+    d = sqrt(xx*xx + yy*yy);
+    if (d > r) {
+        const double d2 = (vignette->edge_dist_factor - 1) * r;
+        const double f = 1 - (d - r) / (d2 ? d2 : 1);
+        av_assert0(f <= 1);
+        return f < 0 ? 0 : f;
+    } else {
+        return 1;
+    }
+}
+
+static int filter_frame(AVFilterLink *inlink, AVFrame *in)
+{
+    int x, y, direct = 0;
+    AVFilterContext *ctx = inlink->dst;
+    VignetteContext *vignette = ctx->priv;
+    AVFilterLink *outlink = inlink->dst->outputs[0];
+    AVFrame *out;
+
+    if (av_frame_is_writable(in)) {
+        direct = 1;
+        out = in;
+    } else {
+        out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
+        if (!out) {
+            av_frame_free(&in);
+            return AVERROR(ENOMEM);
+        }
+        av_frame_copy_props(out, in);
+    }
+
+    vignette->var_values[VAR_T]   = TS2T(in->pts, inlink->time_base);
+    vignette->var_values[VAR_PTS] = TS2D(in->pts);
+
+    vignette->r1 = av_expr_eval(vignette->r1_pexpr, vignette->var_values, NULL);
+    vignette->r2 = av_expr_eval(vignette->r2_pexpr, vignette->var_values, NULL);
+    vignette->x0 = av_expr_eval(vignette->x0_pexpr, vignette->var_values, NULL);
+    vignette->y0 = av_expr_eval(vignette->y0_pexpr, vignette->var_values, NULL);
+
+    if (vignette->desc->flags & PIX_FMT_RGB) {
+        uint8_t       *dst = out->data[0];
+        const uint8_t *src = in ->data[0];
+        const int dst_linesize = out->linesize[0];
+        const int src_linesize = in ->linesize[0];
+
+        for (y = 0; y < inlink->h; y++) {
+            uint8_t       *dstp = dst;
+            const uint8_t *srcp = src;
+
+            for (x = 0; x < inlink->w; x++, dstp += 3, srcp += 3) {
+                double f = get_brightness_factor(vignette, x, y);
+                if (f == 1.) {
+                    if (!direct)
+                        memcpy(dstp, srcp, 3);
+                } else if (f == 0.) {
+                    memset(dstp, 0, 3);
+                } else {
+                    dstp[0] = f * srcp[0];
+                    dstp[1] = f * srcp[1];
+                    dstp[2] = f * srcp[2];
+                }
+            }
+            dst += dst_linesize;
+            src += src_linesize;
+        }
+    } else {
+        int plane;
+
+        for (plane = 0; plane < 4 && in->data[plane]; plane++) {
+            uint8_t       *dst = out->data[plane];
+            const uint8_t *src = in ->data[plane];
+            const int dst_linesize = out->linesize[plane];
+            const int src_linesize = in ->linesize[plane];
+            const int hsub = plane == 0 ? 0 : vignette->desc->log2_chroma_w;
+            const int vsub = plane == 0 ? 0 : vignette->desc->log2_chroma_h;
+
+            for (y = 0; y < inlink->h >> vsub; y++) {
+                uint8_t *dstp = dst;
+                const uint8_t *srcp = src;
+
+                for (x = 0; x < inlink->w >> hsub; x++) {
+                    double f = get_brightness_factor(vignette, x << hsub, y << vsub);
+                    if (plane == 0) *dstp++ = f * *srcp++;
+                    else            *dstp++ = f * (*srcp++ - 127) + 127;
+                }
+                dst += dst_linesize;
+                src += src_linesize;
+            }
+        }
+    }
+
+    vignette->var_values[VAR_N] += 1;
+
+    if (!direct)
+        av_frame_free(&in);
+
+    return ff_filter_frame(outlink, out);
+}
+
+static int config_props(AVFilterLink *inlink)
+{
+    VignetteContext *vignette = inlink->dst->priv;
+
+    vignette->desc = av_pix_fmt_desc_get(inlink->format);
+    vignette->var_values[VAR_W]  = inlink->w;
+    vignette->var_values[VAR_H]  = inlink->h;
+    vignette->var_values[VAR_N]  = 0;
+    vignette->var_values[VAR_TB] = av_q2d(inlink->time_base);
+    vignette->var_values[VAR_R]  = inlink->frame_rate.num == 0 || inlink->frame_rate.den == 0 ?
+        NAN : av_q2d(inlink->frame_rate);
+    return 0;
+}
+
+static const AVFilterPad vignette_inputs[] = {
+    {
+        .name         = "default",
+        .type         = AVMEDIA_TYPE_VIDEO,
+        .filter_frame = filter_frame,
+        .config_props = config_props,
+    },
+    { NULL }
+};
+
+static const AVFilterPad vignette_outputs[] = {
+     {
+         .name = "default",
+         .type = AVMEDIA_TYPE_VIDEO,
+     },
+     { NULL }
+};
+
+static const char *const shorthand[] = { "r1", "r2", "x0", "y0", NULL };
+
+AVFilter avfilter_vf_vignette = {
+    .name          = "vignette",
+    .description   = NULL_IF_CONFIG_SMALL("Make a vignette effect."),
+    .priv_size     = sizeof(VignetteContext),
+    .init          = init,
+    .uninit        = uninit,
+    .query_formats = query_formats,
+    .inputs        = vignette_inputs,
+    .outputs       = vignette_outputs,
+    .priv_class    = &vignette_class,
+    .shorthand     = shorthand,
+};
-- 
1.8.2



More information about the ffmpeg-devel mailing list