From patchwork Sat Jul 18 16:12:03 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: 21180 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 E51A544B54E for ; Sat, 18 Jul 2020 19:19:12 +0300 (EEST) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id C227C68B79C; Sat, 18 Jul 2020 19:19:12 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-ed1-f67.google.com (mail-ed1-f67.google.com [209.85.208.67]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 446F368B654 for ; Sat, 18 Jul 2020 19:19:06 +0300 (EEST) Received: by mail-ed1-f67.google.com with SMTP id d16so9907218edz.12 for ; Sat, 18 Jul 2020 09:19:06 -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=W4qyCbhbeUOWxZIIBUk6M0igMQloTWdVoCEt9s/KeFQ=; b=bJVQ2nGbVsCXMZNJl/srdt0DrmKTotV+HPqNMmXbiUzbBDbC70THwQILh22HjrTzkw nCrZo/RKgtLoXHAL7Sw47oodS36Cb07emveP7+D4O4+BQ5XrqRVZFitPMLTYZy8yFzzP /csusoIhEvcsE2pkrui3wmhVRkpvW2+QO6aTOu2y7nQ+y75Oyy57QPipFj12B2oLxBa4 iG3XR2npiCDnR6TzUS/buNH1RfXdk/chnn39wx0hh3/TeayXKFveL0+f61gYUTKaDZzk IURZS6Z57/g/CwSi9LrQ2w/xZMXxkmbYs5IZfhds4kf5ARX87ioJqSfthVGZlqUVFpys FUEg== 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=W4qyCbhbeUOWxZIIBUk6M0igMQloTWdVoCEt9s/KeFQ=; b=qhbsIceWtQFh6Bk6x+ZR/x2VJnz7mGZwQK7RyoCNTWffBoZnAwD3sLnDXu1vqdZSZG ygo2ENelYz7BMwY3tl+VjCNtH9uy+6rkE7IJL8fiRfkqAhCBcVqUwNsShTf9/mIYSmpr YsbTFqyKIh8gMvE9TGSWMHb1womhNxIHrhnXYjV4RfovORLSiMoTNjmQzNEIW5ZpsMpT 87tzW0853gYdoUBySsKGeeNEvjfHAlbJ5vF7SgSKoCMkvhLZJNpywEleRyCyfD32pErI fDXOoc+S8dvvKizYbeFCBXS9FdIjnr3ViKwgLobiMqiazgGWXWv+Cwh7pMtFUXnyL7E+ 9e6Q== X-Gm-Message-State: AOAM533/2cV+u9NoVZx0HCPNGHA3wSZMkbWGN78YwOnTiKzinHJAZoym sYvXNTd0y92q/YxjfbBCeBsKNyZ5HZk= X-Google-Smtp-Source: ABdhPJx12koBTUQR1RG1Tk7Ss5lSLJBNXXW/rd79XqSJro07Xcg5jPRybJ5ECDzLxIvbEGR18SIPsQ== X-Received: by 2002:a05:6402:1494:: with SMTP id e20mr14040988edv.2.1595088739060; Sat, 18 Jul 2020 09:12:19 -0700 (PDT) Received: from localhost.localdomain ([94.250.186.89]) by smtp.gmail.com with ESMTPSA id u13sm11414106eds.10.2020.07.18.09.12.17 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 18 Jul 2020 09:12:18 -0700 (PDT) From: Paul B Mahol To: ffmpeg-devel@ffmpeg.org Date: Sat, 18 Jul 2020 18:12:03 +0200 Message-Id: <20200718161203.16643-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 | 560 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 610 insertions(+) create mode 100644 libavfilter/vf_rblur.c diff --git a/doc/filters.texi b/doc/filters.texi index a2c31ff610..62705db984 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 a 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 amount +Set amount of circular blur. Default is @code{0.03}. + +@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 a 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 amount +Set amount of radial blur. Default is @code{0.03}. + +@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..080224bcf7 --- /dev/null +++ b/libavfilter/vf_rblur.c @@ -0,0 +1,560 @@ +/* + * 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 amount; + 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 }, + { "amount", "set blur amount", OFFSET(amount), AV_OPT_TYPE_FLOAT, {.dbl=0.03}, 0, 1, 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->amount * (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->amount * (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; + float u, v; + int iX, iY; + int X, Y; + float k[4]; + int p[4]; + + polar_to_cart(x, (y / (bheight - 1.f)) * 2.f * M_PI - M_PI, &fX, &fY); + iX = floorf(fX); + iY = floorf(fY); + u = fX - iX; + v = fY - iY; + k[0] = (1.f - u) * (1.f - v); + k[1] = u * (1.f - v); + k[2] = (1.f - u) * v; + k[3] = u * v; + X = av_clip(iX + center_x, 0, width - 1); + Y = av_clip(iY + center_y, 0, height - 1); + p[0] = src[Y * in_linesize + X]; + X = av_clip(iX + center_x + 1, 0, width - 1); + p[1] = src[Y * in_linesize + X]; + X = av_clip(iX + center_x, 0, width - 1); + Y = av_clip(iY + center_y + 1, 0, height - 1); + p[2] = src[Y * in_linesize + X]; + X = av_clip(iX + center_x + 1, 0, width - 1); + p[3] = src[Y * in_linesize + X]; + bptr[x] = p[0] * k[0] + p[1] * k[1] + p[2] * k[2] + p[3] * k[3]; + } + bptr += bwidth; + } + } else { + for (int y = slice_start; y < slice_end; y++) { + for (int x = 0; x < bwidth; x++) { + float fX, fY; + float u, v; + int iX, iY; + int X, Y; + float k[4]; + int p[4]; + + polar_to_cart(x, (y / (bheight - 1.f)) * 2.f * M_PI - M_PI, &fX, &fY); + iX = floorf(fX); + iY = floorf(fY); + u = fX - iX; + v = fY - iY; + k[0] = (1.f - u) * (1.f - v); + k[1] = u * (1.f - v); + k[2] = (1.f - u) * v; + k[3] = u * v; + X = av_clip(iX + center_x, 0, width - 1); + Y = av_clip(iY + center_y, 0, height - 1); + p[0] = src16[Y * in_linesize / 2 + X]; + X = av_clip(iX + center_x + 1, 0, width - 1); + p[1] = src16[Y * in_linesize / 2 + X]; + X = av_clip(iX + center_x, 0, width - 1); + Y = av_clip(iY + center_y + 1, 0, height - 1); + p[2] = src16[Y * in_linesize / 2 + X]; + X = av_clip(iX + center_x + 1, 0, width - 1); + p[3] = src16[Y * in_linesize / 2 + X]; + bptr[x] = p[0] * k[0] + p[1] * k[1] + p[2] * k[2] + p[3] * k[3]; + } + 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; + float u, v; + int iX, iY; + int X, Y; + float k[4]; + int p[4]; + + cart_to_polar(x - center_x, y - center_y, &fX, &fY); + iX = floorf(fX); + fY = ((fY + M_PI) / (2.f * M_PI)) * (bheight - 1); + iY = floorf(fY); + u = fX - iX; + v = fY - iY; + k[0] = (1.f - u) * (1.f - v); + k[1] = u * (1.f - v); + k[2] = (1.f - u) * v; + k[3] = u * v; + X = av_clip(iX, 0, bwidth - 1); + Y = av_clip(iY, 0, bheight - 1); + p[0] = bptr[Y * bwidth + X]; + X = av_clip(iX + 1, 0, bwidth - 1); + Y = av_clip(iY, 0, bheight - 1); + p[1] = bptr[Y * bwidth + X]; + X = av_clip(iX, 0, bwidth - 1); + Y = av_clip(iY + 1, 0, bheight - 1); + p[2] = bptr[Y * bwidth + X]; + X = av_clip(iX + 1, 0, bwidth - 1); + Y = av_clip(iY + 1, 0, bheight - 1); + p[3] = bptr[Y * bwidth + X]; + dst[x] = p[0] * k[0] + p[1] * k[1] + p[2] * k[2] + p[3] * k[3]; + } + dst += out_linesize; + } + } else { + for (int y = slice_start; y < slice_end; y++) { + for (int x = 0; x < width; x++) { + float fX, fY; + float u, v; + int iX, iY; + int X, Y; + float k[4]; + int p[4]; + + cart_to_polar(x - center_x, y - center_y, &fX, &fY); + iX = floorf(fX); + fY = ((fY + M_PI) / (2.f * M_PI)) * (bheight - 1); + iY = floorf(fY); + u = fX - iX; + v = fY - iY; + k[0] = (1.f - u) * (1.f - v); + k[1] = u * (1.f - v); + k[2] = (1.f - u) * v; + k[3] = u * v; + X = av_clip(iX, 0, bwidth - 1); + Y = av_clip(iY, 0, bheight - 1); + p[0] = bptr[Y * bwidth + X]; + X = av_clip(iX + 1, 0, bwidth - 1); + Y = av_clip(iY, 0, bheight - 1); + p[1] = bptr[Y * bwidth + X]; + X = av_clip(iX, 0, bwidth - 1); + Y = av_clip(iY + 1, 0, bheight - 1); + p[2] = bptr[Y * bwidth + X]; + X = av_clip(iX + 1, 0, bwidth - 1); + Y = av_clip(iY + 1, 0, bheight - 1); + p[3] = bptr[Y * bwidth + X]; + dst16[x] = p[0] * k[0] + p[1] * k[1] + p[2] * k[2] + p[3] * k[3]; + } + 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->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 */