[FFmpeg-devel] [PATCH 2/2] lavfi/stackblur: add stackblur filter

Paul B Mahol onemda at gmail.com
Wed Nov 20 14:38:12 EET 2019


How can this be fast at all?
It does not use slice threading and also supports only packed rgb formats.
Have you actually benchmarked with and without lookup table?

On 11/13/19, Jun Zhao <mypopydev at gmail.com> wrote:
> From: Jun Zhao <barryjzhao at tencent.com>
>
> add stackblur filter
>
> Signed-off-by: Jun Zhao <barryjzhao at tencent.com>
> ---
>  doc/filters.texi           |   22 +++
>  libavfilter/Makefile       |    1 +
>  libavfilter/allfilters.c   |    1 +
>  libavfilter/vf_stackblur.c |  362
> ++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 386 insertions(+), 0 deletions(-)
>  create mode 100644 libavfilter/vf_stackblur.c
>
> diff --git a/doc/filters.texi b/doc/filters.texi
> index 23a3ded..0c450ff 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -17132,6 +17132,28 @@ ffmpeg -i main.mpg -i ref.mkv -lavfi
> "[0:v]settb=1/AVTB,setpts=PTS-STARTPTS[mai
>  @end example
>  @end itemize
>
> + at section stackblur
> +
> +Blur the input image with stack blur algorithm, this is a compromise
> between Gaussian Blur
> +and Box blur, It creates much better looking blurs than Box Blur, but is
> faster
> +than the Gaussian Blur.
> +
> +Called it stack blur because this describes best how this filter works
> internally: it creates
> +a kind of moving stack of colors whilst scanning through the image. Thereby
> it
> +just has to add one new block of color to the right side of the stack and
> remove the
> +leftmost color. The remaining colors on the topmost layer of the stack are
> either added on
> +or reduced by one, depending on if they are on the right or on the left
> side of the stack.
> +
> +This filter accepts the following options:
> +
> + at table @option
> + at item radius
> + at item r
> +Set the blurring box radius. The option value must be a int number in
> +the range [1, 179] that specifies the blur box size of the stack blur
> filter
> +used to blur the image. Default value is @code{2}.
> + at end table
> +
>  @section stereo3d
>
>  Convert between different stereoscopic image formats.
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index db4d5e6..b9a4ad0 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -392,6 +392,7 @@ OBJS-$(CONFIG_SPLIT_FILTER)                  += split.o
>  OBJS-$(CONFIG_SPP_FILTER)                    += vf_spp.o
>  OBJS-$(CONFIG_SR_FILTER)                     += vf_sr.o
>  OBJS-$(CONFIG_SSIM_FILTER)                   += vf_ssim.o framesync.o
> +OBJS-$(CONFIG_STACKBLUR_FILTER)              += vf_stackblur.o
>  OBJS-$(CONFIG_STEREO3D_FILTER)               += vf_stereo3d.o
>  OBJS-$(CONFIG_STREAMSELECT_FILTER)           += f_streamselect.o
> framesync.o
>  OBJS-$(CONFIG_SUBTITLES_FILTER)              += vf_subtitles.o
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index d507bc5..4064ba0 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -373,6 +373,7 @@ extern AVFilter ff_vf_split;
>  extern AVFilter ff_vf_spp;
>  extern AVFilter ff_vf_sr;
>  extern AVFilter ff_vf_ssim;
> +extern AVFilter ff_vf_stackblur;
>  extern AVFilter ff_vf_stereo3d;
>  extern AVFilter ff_vf_streamselect;
>  extern AVFilter ff_vf_subtitles;
> diff --git a/libavfilter/vf_stackblur.c b/libavfilter/vf_stackblur.c
> new file mode 100644
> index 0000000..f1a6f20
> --- /dev/null
> +++ b/libavfilter/vf_stackblur.c
> @@ -0,0 +1,362 @@
> +/*
> + * Copyright (c) 2019 Jun Zhao
> + *
> + * 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
> + */
> +
> +/**
> + * @file
> + * Stack blur filter
> + *
> + * Stack Blur Algorithm by Mario Klingemann <mario at quasimondo.com>
> + *
> + * @see http://incubator.quasimondo.com/processing/stackblur.pde
> + */
> +
> +#include "libavutil/avassert.h"
> +#include "libavutil/imgutils.h"
> +#include "libavutil/opt.h"
> +#include "avfilter.h"
> +#include "formats.h"
> +#include "internal.h"
> +#include "video.h"
> +
> +typedef struct SuperFastBlurContext {
> +    const AVClass *class;
> +
> +    int radius;
> +
> +    uint32_t *vMIN;
> +    uint8_t *rgb;
> +    uint8_t *dv;
> +
> +    int *stack;
> +} StackBlurContext;
> +
> +#define OFFSET(x) offsetof(StackBlurContext, x)
> +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
> +static const AVOption stackblur_options[] = {
> +    { "radius", "Radius of the stack blurring box", OFFSET(radius),
> AV_OPT_TYPE_INT, {.i64 = 2}, 1, 179, FLAGS },
> +    { "r",      "Radius of the stack blurring box", OFFSET(radius),
> AV_OPT_TYPE_INT, {.i64 = 2}, 1, 179, FLAGS },
> +    { NULL }
> +};
> +
> +AVFILTER_DEFINE_CLASS(stackblur);
> +
> +static av_cold int init(AVFilterContext *ctx)
> +{
> +    StackBlurContext *s = ctx->priv;
> +
> +    // This line precalculates a lookup table for all the possible
> +    // mean values that can occur. This is to avoid costly division
> +    // in the inner loop. On some systems doing the division directly
> +    // instead of a doing an array lookup might actually be faster
> +    // nowadays.
> +    uint32_t div = 2 * s->radius + 1;
> +    int divsum = (div + 1) >> 1;
> +    divsum *= divsum;
> +    s->dv = av_malloc(256 * divsum * sizeof(*s->dv));
> +    if (!s->dv)
> +        return AVERROR(ENOMEM);
> +    for (int i = 0; i < 256 * divsum; i++)
> +        s->dv[i] = i / divsum;
> +
> +    return 0;
> +}
> +
> +static int query_formats(AVFilterContext *ctx)
> +{
> +    static const enum AVPixelFormat pix_fmts[] = {
> +        AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24,
> +        AV_PIX_FMT_NONE
> +    };
> +
> +    AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts);
> +    if (!fmts_list)
> +        return AVERROR(ENOMEM);
> +    return ff_set_common_formats(ctx, fmts_list);
> +}
> +
> +static int config_props(AVFilterLink *inlink)
> +{
> +    AVFilterContext *ctx = inlink->dst;
> +    StackBlurContext *s = ctx->priv;
> +
> +    uint32_t div = 2 * s->radius + 1;
> +    uint32_t wh = inlink->w * inlink->h;
> +
> +    s->rgb   = av_malloc(sizeof(*s->rgb) * wh * 3);
> +    s->vMIN  = av_malloc(FFMAX(inlink->w, inlink->h) * sizeof(*s->vMIN));
> +    s->stack = av_malloc(div * 3 * sizeof(*s->stack));
> +    if (!s->vMIN || !s->rgb || !s->stack)
> +        return AVERROR(ENOMEM);
> +
> +    return 0;
> +}
> +
> +// Stack Blur v1.0
> +//
> +// Author: Mario Klingemann <mario at quasimondo.com>
> +// http://incubator.quasimondo.com
> +// created Feburary 29, 2004
> +//
> +// This is a compromise between Gaussian Blur and Box blur
> +// It creates much better looking blurs than Box Blur, but is faster
> +// than the Gaussian Blur implementation.
> +//
> +// Called it Stack Blur because this describes best how this
> +// filter works internally: it creates a kind of moving stack
> +// of colors whilst scanning through the image. Thereby it
> +// just has to add one new block of color to the right side
> +// of the stack and remove the leftmost color. The remaining
> +// colors on the topmost layer of the stack are either added on
> +// or reduced by one, depending on if they are on the right or
> +// on the left side of the stack.
> +//
> +static void stack_blur(StackBlurContext *s, uint8_t *pix, int w, int h, int
> nb_comps)
> +{
> +    uint32_t wm = w - 1;
> +    uint32_t hm = h - 1;
> +    uint32_t wh = w * h;
> +
> +    int radius = s->radius;
> +
> +    uint8_t *rgb = s->rgb;
> +    uint8_t *r = rgb;
> +    uint8_t *g = rgb + wh;
> +    uint8_t *b = rgb + wh * 2;
> +    int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
> +
> +    uint32_t stackpointer;
> +    uint32_t stackstart;
> +    int *sir;
> +    int rbs;
> +    int r1 = radius + 1;
> +    int routsum, goutsum, boutsum;
> +    int rinsum, ginsum, binsum;
> +
> +    uint32_t div = 2 * radius + 1;
> +
> +    int(*stack)[3] = (int(*)[3])(s->stack);
> +    uint32_t *vMIN = s->vMIN;
> +    uint8_t *dv = s->dv;
> +
> +    yw = yi = 0;
> +
> +    for (y = 0; y < h; y++) {
> +        rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum =
> gsum = bsum = 0;
> +        for (i = -radius; i <= radius; i++) {
> +            p = yi + (FFMIN(wm, FFMAX(i, 0)));
> +            sir = stack[i + radius];
> +            sir[0] = pix[(p*nb_comps)];
> +            sir[1] = pix[(p*nb_comps) + 1];
> +            sir[2] = pix[(p*nb_comps) + 2];
> +
> +            rbs = r1 - FFABS(i);
> +            rsum += sir[0] * rbs;
> +            gsum += sir[1] * rbs;
> +            bsum += sir[2] * rbs;
> +            if (i > 0) {
> +                rinsum += sir[0];
> +                ginsum += sir[1];
> +                binsum += sir[2];
> +            } else {
> +                routsum += sir[0];
> +                goutsum += sir[1];
> +                boutsum += sir[2];
> +            }
> +        }
> +        stackpointer = radius;
> +
> +        for (x = 0; x < w; x++) {
> +            r[yi] = dv[rsum];
> +            g[yi] = dv[gsum];
> +            b[yi] = dv[bsum];
> +
> +            rsum -= routsum;
> +            gsum -= goutsum;
> +            bsum -= boutsum;
> +
> +            stackstart = stackpointer - radius + div;
> +            sir = stack[stackstart % div];
> +
> +            routsum -= sir[0];
> +            goutsum -= sir[1];
> +            boutsum -= sir[2];
> +
> +            if (y == 0)
> +                vMIN[x] = FFMIN(x + radius + 1, wm);
> +            p = yw + vMIN[x];
> +
> +            sir[0] = pix[(p*nb_comps)];
> +            sir[1] = pix[(p*nb_comps) + 1];
> +            sir[2] = pix[(p*nb_comps) + 2];
> +            rinsum += sir[0];
> +            ginsum += sir[1];
> +            binsum += sir[2];
> +
> +            rsum += rinsum;
> +            gsum += ginsum;
> +            bsum += binsum;
> +
> +            stackpointer = (stackpointer + 1) % div;
> +            sir = stack[(stackpointer) % div];
> +
> +            routsum += sir[0];
> +            goutsum += sir[1];
> +            boutsum += sir[2];
> +
> +            rinsum -= sir[0];
> +            ginsum -= sir[1];
> +            binsum -= sir[2];
> +
> +            yi++;
> +        }
> +        yw += w;
> +    }
> +    for (x = 0; x < w; x++) {
> +        rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum =
> gsum = bsum = 0;
> +        yp = -radius * w;
> +        for (i = -radius; i <= radius; i++) {
> +            yi = FFMAX(0, yp) + x;
> +
> +            sir = stack[i + radius];
> +
> +            sir[0] = r[yi];
> +            sir[1] = g[yi];
> +            sir[2] = b[yi];
> +
> +            rbs = r1 - FFABS(i);
> +
> +            rsum += r[yi] * rbs;
> +            gsum += g[yi] * rbs;
> +            bsum += b[yi] * rbs;
> +
> +            if (i > 0) {
> +                rinsum += sir[0];
> +                ginsum += sir[1];
> +                binsum += sir[2];
> +            } else {
> +                routsum += sir[0];
> +                goutsum += sir[1];
> +                boutsum += sir[2];
> +            }
> +
> +            if (i < hm)
> +                yp += w;
> +        }
> +        yi = x;
> +        stackpointer = radius;
> +        for (y = 0; y < h; y++) {
> +            pix[(yi*nb_comps)]     = dv[rsum];
> +            pix[(yi*nb_comps) + 1] = dv[gsum];
> +            pix[(yi*nb_comps) + 2] = dv[bsum];
> +            rsum -= routsum;
> +            gsum -= goutsum;
> +            bsum -= boutsum;
> +
> +            stackstart = stackpointer - radius + div;
> +            sir = stack[stackstart % div];
> +
> +            routsum -= sir[0];
> +            goutsum -= sir[1];
> +            boutsum -= sir[2];
> +
> +            if (x == 0)
> +                vMIN[y] = FFMIN(y + r1, hm) * w;
> +            p = x + vMIN[y];
> +
> +            sir[0] = r[p];
> +            sir[1] = g[p];
> +            sir[2] = b[p];
> +
> +            rinsum += sir[0];
> +            ginsum += sir[1];
> +            binsum += sir[2];
> +
> +            rsum += rinsum;
> +            gsum += ginsum;
> +            bsum += binsum;
> +
> +            stackpointer = (stackpointer + 1) % div;
> +            sir = stack[stackpointer];
> +
> +            routsum += sir[0];
> +            goutsum += sir[1];
> +            boutsum += sir[2];
> +
> +            rinsum -= sir[0];
> +            ginsum -= sir[1];
> +            binsum -= sir[2];
> +
> +            yi += w;
> +        }
> +    }
> +}
> +
> +static int filter_frame(AVFilterLink *inlink, AVFrame *in)
> +{
> +    AVFilterContext *ctx = inlink->dst;
> +    StackBlurContext *s = ctx->priv;
> +    AVFilterLink *outlink = ctx->outputs[0];
> +    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
> +
> +    stack_blur(s, in->data[0], inlink->w, inlink->h, desc->nb_components);
> +
> +    return ff_filter_frame(outlink, in);
> +}
> +
> +static av_cold void uninit(AVFilterContext *ctx)
> +{
> +    StackBlurContext *s = ctx->priv;
> +
> +    av_freep(&s->rgb);
> +    av_freep(&s->vMIN);
> +    av_freep(&s->dv);
> +    av_freep(&s->stack);
> +}
> +
> +static const AVFilterPad stackblur_inputs[] = {
> +    {
> +        .name         = "default",
> +        .type         = AVMEDIA_TYPE_VIDEO,
> +        .config_props = config_props,
> +        .filter_frame = filter_frame,
> +    },
> +    { NULL }
> +};
> +
> +static const AVFilterPad stackblur_outputs[] = {
> +    {
> +        .name = "default",
> +        .type = AVMEDIA_TYPE_VIDEO,
> +    },
> +    { NULL }
> +};
> +
> +AVFilter ff_vf_stackblur = {
> +    .name          = "stackblur",
> +    .description   = NULL_IF_CONFIG_SMALL("Blur the input with stack
> algorithm."),
> +    .priv_size     = sizeof(StackBlurContext),
> +    .init          = init,
> +    .uninit        = uninit,
> +    .query_formats = query_formats,
> +    .inputs        = stackblur_inputs,
> +    .outputs       = stackblur_outputs,
> +    .priv_class    = &stackblur_class,
> +    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
> +};
> --
> 1.7.1
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request at ffmpeg.org with subject "unsubscribe".


More information about the ffmpeg-devel mailing list