From patchwork Sun Jul 12 11:01:07 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul B Mahol X-Patchwork-Id: 20973 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id EE11244B9B3 for ; Sun, 12 Jul 2020 14:01:22 +0300 (EEST) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id C9B2768828E; Sun, 12 Jul 2020 14:01:22 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-ed1-f66.google.com (mail-ed1-f66.google.com [209.85.208.66]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 0B2B1680B4C for ; Sun, 12 Jul 2020 14:01:17 +0300 (EEST) Received: by mail-ed1-f66.google.com with SMTP id dm19so8210305edb.13 for ; Sun, 12 Jul 2020 04:01:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:subject:date:message-id; bh=/P3sHPSjjEldbScP/QVRBpVo3/ktWowyC5GnXOc5Esw=; b=XnQdi/H3t3F9On/WWggLnnhRvdCK6IWcc3VK6Mic1AACXcCKdINekF3ALjN2FBFeK+ LEE2mzUiOS+PHXw3hpNAa1ft55fl/cLd2MBy3DKQ5UpTU5CSGudw+y5d+zqLTd6tvT88 R9JnD2+8Ngz0CuNOSTtXJyRv80pokS1c0289vVuWGeQPtsUQxPPYA/FosyyKjfnICQm3 kA2/pOXi8JMnRB3f4WrJSnWFDl7Urd3jgK36pdT02IC5b0VzjZJsyYEFv7uSp+DNN7zf fw6OauLEIKO+TjGlvkorB9LWLtCP6gwGMzgibGYhDhswSmSzL8Zz8L/+u17nJAcuEPLp aklg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id; bh=/P3sHPSjjEldbScP/QVRBpVo3/ktWowyC5GnXOc5Esw=; b=DnbPikkj/SFt7WOEgmPlue+1ABfgosGlUhkbrI3/V6hoNuRFiVrVK5kSrdsscgQmXd bkJP7SCCt65LkRdnBfZpP0iHJ8dO7o2YvvMKksTwAxkZtlLs3AGvhD3aqgrem2J0DE1q ymOSA/QKVk3iMMybTSXtMJFXDGHC7PEAjFj8Kp68WUk5D6xGSGi4JSDATLEi2nC1kXhN 2bua+FQ0eZpMGQv5+eJrQVyaoKR7AviiezXpwXFksUOSLatm62KHEQjkYBlBlN3lDanr 2Ca61V4/r6O892exz/fzuoQxPOtP+xDXRVBWL49aooAqWtOr6qB5z3seHe8U/ONZZv8S kV5w== X-Gm-Message-State: AOAM530MoX+9nzQWJZLYMPTKhxaqembe606k3VbKHqepQXffhYxkdD49 kajRSv2CQDBmVDCC6RyOJ2r33oYR9P8= X-Google-Smtp-Source: ABdhPJx/yFCPrbkgptN5+LnqiMGuNs3NVK+N54BsW+914Fse4Xxwn50FbGJ+0CjtGqyCnL39qdvEjw== X-Received: by 2002:a50:8d5a:: with SMTP id t26mr90858973edt.282.1594551675654; Sun, 12 Jul 2020 04:01:15 -0700 (PDT) Received: from localhost.localdomain ([37.244.250.8]) by smtp.gmail.com with ESMTPSA id fi29sm7279779ejb.83.2020.07.12.04.01.14 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 12 Jul 2020 04:01:15 -0700 (PDT) From: Paul B Mahol To: ffmpeg-devel@ffmpeg.org Date: Sun, 12 Jul 2020 13:01:07 +0200 Message-Id: <20200712110107.6656-1-onemda@gmail.com> X-Mailer: git-send-email 2.17.1 Subject: [FFmpeg-devel] [PATCH] avfilter: add radial and circular blur video filter X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Signed-off-by: Paul B Mahol --- doc/filters.texi | 46 ++++ libavfilter/Makefile | 2 + libavfilter/allfilters.c | 2 + libavfilter/vf_rblur.c | 476 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 526 insertions(+) create mode 100644 libavfilter/vf_rblur.c diff --git a/doc/filters.texi b/doc/filters.texi index ccc066a9ab..8c5006ce04 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -7231,6 +7231,29 @@ Set planes to filter. Default value is to filter all planes except alpha plane. @end table +@section cblur +Apply Circular blur filter. + +The filter accepts the following options: + +@table @option +@item centerx, centery +Set central point position of circular blur. Default is @code{0.5}. + +@item angle +Set angle of circular blur in degrees. Default is @code{5}. + +@item planes +Set which planes to filter. By default all planes are filtered. +@end table + +@subsection Commands +This filter supports same @ref{commands} as options. +The command accepts the same syntax of the corresponding option. + +If the specified expression is not valid, it is kept at its current +value. + @section chromahold Remove all color information for all colors except for certain one. @@ -15768,6 +15791,29 @@ less than @code{0}, the filter will try to use a good random seed on a best effort basis. @end table +@section rblur +Apply Radial blur filter. + +The filter accepts the following options: + +@table @option +@item centerx, centery +Set central point position of radial blur. Default is @code{0.5}. + +@item angle +Set angle of radial blur in degrees. Default is @code{5}. + +@item planes +Set which planes to filter. By default all planes are filtered. +@end table + +@subsection Commands +This filter supports same @ref{commands} as options. +The command accepts the same syntax of the corresponding option. + +If the specified expression is not valid, it is kept at its current +value. + @section readeia608 Read closed captioning (EIA-608) information from the top lines of a video frame. diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 33dcc69392..30f2abf9e2 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -180,6 +180,7 @@ OBJS-$(CONFIG_BOXBLUR_OPENCL_FILTER) += vf_avgblur_opencl.o opencl.o \ opencl/avgblur.o boxblur.o OBJS-$(CONFIG_BWDIF_FILTER) += vf_bwdif.o yadif_common.o OBJS-$(CONFIG_CAS_FILTER) += vf_cas.o +OBJS-$(CONFIG_CBLUR_FILTER) += vf_rblur.o OBJS-$(CONFIG_CHROMABER_VULKAN_FILTER) += vf_chromaber_vulkan.o vulkan.o OBJS-$(CONFIG_CHROMAHOLD_FILTER) += vf_chromakey.o OBJS-$(CONFIG_CHROMAKEY_FILTER) += vf_chromakey.o @@ -358,6 +359,7 @@ OBJS-$(CONFIG_PSNR_FILTER) += vf_psnr.o framesync.o OBJS-$(CONFIG_PULLUP_FILTER) += vf_pullup.o OBJS-$(CONFIG_QP_FILTER) += vf_qp.o OBJS-$(CONFIG_RANDOM_FILTER) += vf_random.o +OBJS-$(CONFIG_RBLUR_FILTER) += vf_rblur.o OBJS-$(CONFIG_READEIA608_FILTER) += vf_readeia608.o OBJS-$(CONFIG_READVITC_FILTER) += vf_readvitc.o OBJS-$(CONFIG_REALTIME_FILTER) += f_realtime.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index b896a76c9e..38279a44eb 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -171,6 +171,7 @@ extern AVFilter ff_vf_boxblur; extern AVFilter ff_vf_boxblur_opencl; extern AVFilter ff_vf_bwdif; extern AVFilter ff_vf_cas; +extern AVFilter ff_vf_cblur; extern AVFilter ff_vf_chromahold; extern AVFilter ff_vf_chromakey; extern AVFilter ff_vf_chromanr; @@ -341,6 +342,7 @@ extern AVFilter ff_vf_psnr; extern AVFilter ff_vf_pullup; extern AVFilter ff_vf_qp; extern AVFilter ff_vf_random; +extern AVFilter ff_vf_rblur; extern AVFilter ff_vf_readeia608; extern AVFilter ff_vf_readvitc; extern AVFilter ff_vf_realtime; diff --git a/libavfilter/vf_rblur.c b/libavfilter/vf_rblur.c new file mode 100644 index 0000000000..2524319b05 --- /dev/null +++ b/libavfilter/vf_rblur.c @@ -0,0 +1,476 @@ +/* + * Copyright (c) 2020 Paul B Mahol + * + * 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/imgutils.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "avfilter.h" +#include "formats.h" +#include "internal.h" +#include "video.h" + +typedef struct RBlurContext { + const AVClass *class; + + float centerx, centery; + float angle; + float angle_rad; + int planes; + int cblur; + int center_x[4], center_y[4]; + + int depth; + int planewidth[4]; + int planeheight[4]; + int buffer_w[4], buffer_h[4]; + float *buffer, *buffer_out; + int nb_planes; + int (*filter)(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs); +} RBlurContext; + +typedef struct ThreadData { + AVFrame *in, *out; + int height; + int width; + int plane; +} ThreadData; + +#define OFFSET(x) offsetof(RBlurContext, x) +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_RUNTIME_PARAM + +static const AVOption rblur_options[] = { + { "centerx", "set center X position", OFFSET(centerx), AV_OPT_TYPE_FLOAT, {.dbl=0.5}, 0, 1, FLAGS }, + { "centery", "set center Y position", OFFSET(centery), AV_OPT_TYPE_FLOAT, {.dbl=0.5}, 0, 1, FLAGS }, + { "angle", "set angle", OFFSET(angle), AV_OPT_TYPE_FLOAT, {.dbl=5}, 0.0, 360, FLAGS }, + { "planes", "set planes to filter", OFFSET(planes), AV_OPT_TYPE_INT, {.i64=0xF}, 0, 0xF, FLAGS }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(rblur); + +static void cart_to_polar(float x, float y, float *r, float *a) +{ + *r = sqrtf(x * x + y * y); + *a = atan2f(y, x); +} + +static void polar_to_cart(float r, float a, float *x, float *y) +{ + *x = r * cosf(a); + *y = r * sinf(a); +} + +static inline int mod(int a, int b) +{ + const int res = a % b; + + if (res < 0) { + return res + b; + } else { + return res; + } +} + +static int filter_horizontally(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + RBlurContext *s = ctx->priv; + ThreadData *td = arg; + const int height = td->height; + const int width = td->width; + const int slice_start = (height * jobnr ) / nb_jobs; + const int slice_end = (height * (jobnr+1)) / nb_jobs; + const int size = (s->angle_rad / (2.f * M_PI)) * (width - 1); + float *buffer = s->buffer_out; + const float *src; + float *ptr; + int y; + + /* Filter horizontally along each row */ + for (y = slice_start; y < slice_end; y++) { + float acc = 0.f; + int count = 0; + + src = (const float *)s->buffer + width * y; + ptr = buffer + width * y; + + for (int i = 0; i <= size; i++) { + acc += src[mod(i, width)]; + count++; + } + + for (int i = 0; i < width; i++) { + ptr[mod(i + size / 2, width)] = acc / count; + acc += src[mod(i + size + 1, width)] - src[i]; + } + } + + return 0; +} + +static int filter_vertically(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + RBlurContext *s = ctx->priv; + ThreadData *td = arg; + const int height = td->height; + const int width = td->width; + const int slice_start = (width * jobnr ) / nb_jobs; + const int slice_end = (width * (jobnr+1)) / nb_jobs; + const int size = (s->angle_rad / (2.f * M_PI)) * (height - 1); + float *buffer = s->buffer_out; + const float *src; + float *ptr; + int x; + + /* Filter vertically along each column */ + for (x = slice_start; x < slice_end; x++) { + int count = 0; + float acc = 0.f; + + ptr = buffer + x; + src = s->buffer + x; + + for (int i = 0; i <= size; i++) { + acc += src[mod(i, height) * width]; + count++; + } + + for (int i = 0; i < height; i++) { + ptr[mod(i + size / 2, height) * width] = acc / count; + acc += src[mod(i + size + 1, height) * width] - src[i * width]; + } + } + + return 0; +} + +static int p2c(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + RBlurContext *s = ctx->priv; + ThreadData *td = arg; + const int plane = td->plane; + const uint8_t *src = td->in->data[plane]; + const uint16_t *src16 = (const uint16_t *)td->in->data[plane]; + const int in_linesize = td->in->linesize[plane]; + const int height = s->planeheight[plane]; + const int width = s->planewidth[plane]; + const int bheight = s->buffer_h[plane]; + const int bwidth = s->buffer_w[plane]; + const int center_x = s->center_x[plane]; + const int center_y = s->center_y[plane]; + const int slice_start = (bheight * jobnr ) / nb_jobs; + const int slice_end = (bheight * (jobnr+1)) / nb_jobs; + float *bptr = s->buffer + slice_start * bwidth; + + if (s->depth == 8) { + for (int y = slice_start; y < slice_end; y++) { + for (int x = 0; x < bwidth; x++) { + float fX, fY; + int X, Y; + + polar_to_cart(x, (y / (bheight - 1.f)) * 2.f * M_PI - M_PI, &fX, &fY); + X = av_clip(fX + center_x, 0, width - 1); + Y = av_clip(fY + center_y, 0, height - 1); + bptr[x] = src[Y * in_linesize + X]; + } + bptr += bwidth; + } + } else { + for (int y = slice_start; y < slice_end; y++) { + for (int x = 0; x < bwidth; x++) { + float fX, fY; + int X, Y; + + polar_to_cart(x, (y / (bheight - 1.f)) * 2.f * M_PI - M_PI, &fX, &fY); + X = av_clip(fX + center_x, 0, width - 1); + Y = av_clip(fY + center_y, 0, height - 1); + bptr[x] = src16[Y * in_linesize / 2 + X]; + } + bptr += bwidth; + } + } + + return 0; +} + +static int c2p(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + RBlurContext *s = ctx->priv; + ThreadData *td = arg; + const int plane = td->plane; + const int out_linesize = td->out->linesize[plane]; + const int height = s->planeheight[plane]; + const int width = s->planewidth[plane]; + const int bheight = s->buffer_h[plane]; + const int bwidth = s->buffer_w[plane]; + const int center_x = s->center_x[plane]; + const int center_y = s->center_y[plane]; + const int slice_start = (height * jobnr ) / nb_jobs; + const int slice_end = (height * (jobnr+1)) / nb_jobs; + uint8_t *dst = td->out->data[plane] + slice_start * out_linesize; + uint16_t *dst16 = (uint16_t *)td->out->data[plane] + slice_start * out_linesize / 2; + float *bptr = s->buffer_out; + + if (s->depth == 8) { + for (int y = slice_start; y < slice_end; y++) { + for (int x = 0; x < width; x++) { + float fX, fY; + int X, Y; + + cart_to_polar(x - center_x, y - center_y, &fX, &fY); + X = av_clip(fX, 0, bwidth - 1); + Y = av_clip(((fY + M_PI) / (2.f * M_PI)) * (bheight - 1), 0, bheight - 1); + dst[x] = bptr[Y * bwidth + X]; + } + dst += out_linesize; + } + } else { + for (int y = slice_start; y < slice_end; y++) { + for (int x = 0; x < width; x++) { + float fX, fY; + int X, Y; + + cart_to_polar(x - center_x, y - center_y, &fX, &fY); + X = av_clip(fX, 0, bwidth - 1); + Y = av_clip(((fY + M_PI) / (2.f * M_PI)) * (bheight - 1), 0, bheight - 1); + dst16[x] = bptr[Y * bwidth + X]; + } + dst16 += out_linesize / 2; + } + } + + return 0; +} + +static void polar2cart(AVFilterContext *ctx, AVFrame *in, int plane) +{ + RBlurContext *s = ctx->priv; + const int nb_threads = ff_filter_get_nb_threads(ctx); + ThreadData td; + + td.plane = plane; + td.in = in; + + ctx->internal->execute(ctx, p2c, &td, NULL, FFMIN(s->buffer_h[plane], nb_threads)); +} + +static void cart2polar(AVFilterContext *ctx, AVFrame *out, int plane) +{ + RBlurContext *s = ctx->priv; + const int nb_threads = ff_filter_get_nb_threads(ctx); + ThreadData td; + + td.plane = plane; + td.out = out; + + ctx->internal->execute(ctx, c2p, &td, NULL, FFMIN(s->planeheight[plane], nb_threads)); +} + +static void ririir2d(AVFilterContext *ctx, int plane) +{ + RBlurContext *s = ctx->priv; + const int nb_threads = ff_filter_get_nb_threads(ctx); + ThreadData td; + + td.width = s->buffer_w[plane]; + td.height = s->buffer_h[plane]; + + ctx->internal->execute(ctx, s->filter, &td, NULL, FFMIN(td.height, nb_threads)); +} + +static int query_formats(AVFilterContext *ctx) +{ + static const enum AVPixelFormat pix_fmts[] = { + AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P, + AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P, + AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUV420P, + AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P, + AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P, + AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9, + AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10, + AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV440P12, + AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14, + AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16, + AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9, + AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10, + AV_PIX_FMT_YUVA422P12, AV_PIX_FMT_YUVA444P12, + AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16, + AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRP9, AV_PIX_FMT_GBRP10, + AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16, + AV_PIX_FMT_GBRAP, AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16, + AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY9, AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12, AV_PIX_FMT_GRAY14, AV_PIX_FMT_GRAY16, + AV_PIX_FMT_NONE + }; + + return ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); +} + +static void set_params(AVFilterContext *ctx) +{ + RBlurContext *s = ctx->priv; + AVFilterLink *inlink = ctx->inputs[0]; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); + + s->angle_rad = s->angle * M_PI / 180.f; + s->center_x[1] = s->center_x[2] = AV_CEIL_RSHIFT(lrintf(inlink->w * s->centerx), desc->log2_chroma_w); + s->center_x[0] = s->center_x[3] = lrintf(inlink->w * s->centerx); + s->center_y[1] = s->center_y[2] = AV_CEIL_RSHIFT(lrintf(inlink->h * s->centery), desc->log2_chroma_h); + s->center_y[0] = s->center_y[3] = lrintf(inlink->h * s->centery); +} + +static int config_input(AVFilterLink *inlink) +{ + AVFilterContext *ctx = inlink->dst; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); + RBlurContext *s = ctx->priv; + + s->cblur = !strcmp(ctx->filter->name, "cblur"); + + set_params(ctx); + s->depth = desc->comp[0].depth; + s->planewidth[1] = s->planewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w); + s->planewidth[0] = s->planewidth[3] = inlink->w; + s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h); + s->planeheight[0] = s->planeheight[3] = inlink->h; + + s->nb_planes = av_pix_fmt_count_planes(inlink->format); + s->buffer_w[0] = ceilf(hypotf(s->planewidth[0], s->planeheight[0]) / 2.f); + s->buffer_w[1] = ceilf(hypotf(s->planewidth[1], s->planeheight[1]) / 2.f); + s->buffer_w[2] = ceilf(hypotf(s->planewidth[2], s->planeheight[2]) / 2.f); + s->buffer_w[3] = ceilf(hypotf(s->planewidth[3], s->planeheight[3]) / 2.f); + s->buffer_h[0] = s->planewidth[0] + s->planeheight[0]; + s->buffer_h[1] = s->planewidth[1] + s->planeheight[1]; + s->buffer_h[2] = s->planewidth[2] + s->planeheight[2]; + s->buffer_h[3] = s->planewidth[3] + s->planeheight[3]; + + s->buffer = av_malloc_array(s->buffer_w[0], s->buffer_h[0] * sizeof(*s->buffer)); + s->buffer_out = av_malloc_array(s->buffer_w[0], s->buffer_h[0] * sizeof(*s->buffer_out)); + if (!s->buffer || !s->buffer_out) + return AVERROR(ENOMEM); + + s->filter = s->cblur ? filter_vertically : filter_horizontally; + + return 0; +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *in) +{ + AVFilterContext *ctx = inlink->dst; + RBlurContext *s = ctx->priv; + AVFilterLink *outlink = ctx->outputs[0]; + AVFrame *out; + int plane; + + 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 AVERROR(ENOMEM); + } + av_frame_copy_props(out, in); + } + + set_params(ctx); + + for (plane = 0; plane < s->nb_planes; plane++) { + const int height = s->planeheight[plane]; + const int width = s->planewidth[plane]; + + if (!(s->planes & (1 << plane))) { + if (out != in) + av_image_copy_plane(out->data[plane], out->linesize[plane], + in->data[plane], in->linesize[plane], + width * ((s->depth + 7) / 8), height); + continue; + } + + polar2cart(ctx, in, plane); + ririir2d(ctx, plane); + cart2polar(ctx, out, plane); + } + + if (out != in) + av_frame_free(&in); + return ff_filter_frame(outlink, out); +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + RBlurContext *s = ctx->priv; + + av_freep(&s->buffer); + av_freep(&s->buffer_out); +} + +static const AVFilterPad rblur_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = config_input, + .filter_frame = filter_frame, + }, + { NULL } +}; + +static const AVFilterPad rblur_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + }, + { NULL } +}; + +#if CONFIG_RBLUR_FILTER + +AVFilter ff_vf_rblur = { + .name = "rblur", + .description = NULL_IF_CONFIG_SMALL("Apply Radial Blur filter."), + .priv_size = sizeof(RBlurContext), + .priv_class = &rblur_class, + .uninit = uninit, + .query_formats = query_formats, + .inputs = rblur_inputs, + .outputs = rblur_outputs, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS, + .process_command = ff_filter_process_command, +}; + +#endif /* CONFIG_RBLUR_FILTER */ + +#if CONFIG_CBLUR_FILTER + +#define cblur_options rblur_options +AVFILTER_DEFINE_CLASS(cblur); + +AVFilter ff_vf_cblur = { + .name = "cblur", + .description = NULL_IF_CONFIG_SMALL("Apply Circular Blur filter."), + .priv_size = sizeof(RBlurContext), + .priv_class = &cblur_class, + .uninit = uninit, + .query_formats = query_formats, + .inputs = rblur_inputs, + .outputs = rblur_outputs, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS, + .process_command = ff_filter_process_command, +}; + +#endif /* CONFIG_CBLUR_FILTER */