diff mbox series

[FFmpeg-devel] avfilter: add radial and circular blur video filter

Message ID 20200712110107.6656-1-onemda@gmail.com
State Superseded
Headers show
Series [FFmpeg-devel] avfilter: add radial and circular blur video filter
Related show

Checks

Context Check Description
andriy/default pending
andriy/make success Make finished
andriy/make_fate success Make fate finished

Commit Message

Paul B Mahol July 12, 2020, 11:01 a.m. UTC
Signed-off-by: Paul B Mahol <onemda@gmail.com>
---
 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

Comments

Alexander Strasser July 12, 2020, 11:45 a.m. UTC | #1
Hi Paul!

Only doc comments. Feel free to apply the changes when the real
review has completed.

On 2020-07-12 13:01 +0200, Paul B Mahol wrote:
> Signed-off-by: Paul B Mahol <onemda@gmail.com>
> ---
>  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.

I suggest not upper casing circular and using the indefinite article:

    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 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.

To me the sentence sounds a bit weird. I suggest:

    All options are supported as @ref{commands}.

If you prefer, use that instead.

Now on the content itself. It bears the danger, that at some point
options and commands could diverge. So one needs to keep in mind
to update the documenation accordingly; should it ever happen.

I agree that not duplicating anything is preferable for now.


> +The command accepts the same syntax of the corresponding option.

Maybe:

   Each command accepts the syntax of the corresponding option.


> +
> +If the specified expression is not valid, it is kept at its current
> +value.
> +

All my remarks apply too the rblur filter too.


  Alexander

[...]
Paul B Mahol July 17, 2020, 11:52 a.m. UTC | #2
Will apply.

On 7/12/20, Paul B Mahol <onemda@gmail.com> wrote:
> Signed-off-by: Paul B Mahol <onemda@gmail.com>
> ---
>  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
>
Steinar H. Gunderson July 17, 2020, 12:10 p.m. UTC | #3
On Sun, Jul 12, 2020 at 01:01:07PM +0200, Paul B Mahol wrote:
> Signed-off-by: Paul B Mahol <onemda@gmail.com>

Do you have any benchmarks on this? Or image samples? It looks like you are
transforming to polar coordinates, doing a box blur and then transforming
back... how does this compare speed- and quality-wise to the more common
recursive scale+blend approach (which needs no trig)?

/* Steinar */
Paul B Mahol July 17, 2020, 12:24 p.m. UTC | #4
On 7/17/20, Steinar H. Gunderson <steinar+ffmpeg@gunderson.no> wrote:
> On Sun, Jul 12, 2020 at 01:01:07PM +0200, Paul B Mahol wrote:
>> Signed-off-by: Paul B Mahol <onemda@gmail.com>
>
> Do you have any benchmarks on this? Or image samples? It looks like you are
> transforming to polar coordinates, doing a box blur and then transforming
> back... how does this compare speed- and quality-wise to the more common
> recursive scale+blend approach (which needs no trig)?

Can you provide more info about such algorithm?

>
> /* Steinar */
> --
> Homepage: https://www.sesse.net/
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Steinar H. Gunderson July 17, 2020, 2:47 p.m. UTC | #5
On Fri, Jul 17, 2020 at 02:24:44PM +0200, Paul B Mahol wrote:
>> Do you have any benchmarks on this? Or image samples? It looks like you are
>> transforming to polar coordinates, doing a box blur and then transforming
>> back... how does this compare speed- and quality-wise to the more common
>> recursive scale+blend approach (which needs no trig)?
> Can you provide more info about such algorithm?

There are some pointers in this thread:

  https://www.pouet.net/topic.php?which=4964&page=1#c184918

I guess this is the oldest description that I know of:

  http://web.archive.org/web/20040905085427/http://www.themirror.nl/~plek/hypnoglow.txt

For a more IIR-like approach, there's this:

  https://web.archive.org/web/20010606232509/http://www.demo-scene.dk:80/tutorials/radialblur.html

Note that the latter is so old that it predates widespread MMX :-)

/* Steinar */
Paul B Mahol July 17, 2020, 4:54 p.m. UTC | #6
On 7/17/20, Steinar H. Gunderson <steinar+ffmpeg@gunderson.no> wrote:
> On Fri, Jul 17, 2020 at 02:24:44PM +0200, Paul B Mahol wrote:
>>> Do you have any benchmarks on this? Or image samples? It looks like you
>>> are
>>> transforming to polar coordinates, doing a box blur and then transforming
>>> back... how does this compare speed- and quality-wise to the more common
>>> recursive scale+blend approach (which needs no trig)?
>> Can you provide more info about such algorithm?
>
> There are some pointers in this thread:
>
>   https://www.pouet.net/topic.php?which=4964&page=1#c184918
>
> I guess this is the oldest description that I know of:
>
>
> http://web.archive.org/web/20040905085427/http://www.themirror.nl/~plek/hypnoglow.txt
>
> For a more IIR-like approach, there's this:
>
>
> https://web.archive.org/web/20010606232509/http://www.demo-scene.dk:80/tutorials/radialblur.html
>
> Note that the latter is so old that it predates widespread MMX :-)
>

I already encountered last one.
But crucial info is missing. How one build vectors? And what about
circular blur?

> /* Steinar */
> --
> Homepage: https://www.sesse.net/
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Steinar H. Gunderson July 17, 2020, 5:25 p.m. UTC | #7
On Fri, Jul 17, 2020 at 06:54:40PM +0200, Paul B Mahol wrote:
> But crucial info is missing. How one build vectors?

(point - center) * scale_factor + center

> And what about circular blur?

I've never seen anyone try it, but one would assume it can be done by
blending rotations in a similar recursive fashion.

/* Steinar  */
Paul B Mahol July 17, 2020, 6 p.m. UTC | #8
On 7/17/20, Steinar H. Gunderson <steinar+ffmpeg@gunderson.no> wrote:
> On Fri, Jul 17, 2020 at 06:54:40PM +0200, Paul B Mahol wrote:
>> But crucial info is missing. How one build vectors?
>
> (point - center) * scale_factor + center

And how to pick scale_factor, more over how to make this recursive at all?

>
>> And what about circular blur?
>
> I've never seen anyone try it, but one would assume it can be done by
> blending rotations in a similar recursive fashion.
>
> /* Steinar  */
> --
> Homepage: https://www.sesse.net/
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Steinar H. Gunderson July 17, 2020, 6:13 p.m. UTC | #9
On Fri, Jul 17, 2020 at 08:00:31PM +0200, Paul B Mahol wrote:
>>> But crucial info is missing. How one build vectors?
>> (point - center) * scale_factor + center
> And how to pick scale_factor,

It's a user parameter. scale_factor signifies how long the blur is.
1.0 is no blur, 2.0 means every point gets blurred out to twice the distance
from the center.

> more over how to make this recursive at all?

It's only recursive in the formulation; the actual computation is iterative.

  tmp_pic1 = blur_pass(orig_pic, scale_factor);
  tmp_pic2 = blur_pass(tmp_pic1, (scale_factor - 1.0) / 2.0 + 1.0);
  tmp_pic3 = blur_pass(tmp_pic2, (scale_factor - 1.0) / 4.0 + 1.0);
  output_pic = blur_pass(tmp_pic3, (scale_factor - 1.0) / 8.0 + 1.0);

Repeat as many times as desired until you get the quality you want.

You can also be creative with the blend strength if you want a blur that's
not a box blur.

/* Steinar */
Paul B Mahol July 17, 2020, 6:22 p.m. UTC | #10
On 7/17/20, Steinar H. Gunderson <steinar+ffmpeg@gunderson.no> wrote:
> On Fri, Jul 17, 2020 at 08:00:31PM +0200, Paul B Mahol wrote:
>>>> But crucial info is missing. How one build vectors?
>>> (point - center) * scale_factor + center
>> And how to pick scale_factor,
>
> It's a user parameter. scale_factor signifies how long the blur is.
> 1.0 is no blur, 2.0 means every point gets blurred out to twice the distance
> from the center.
>
>> more over how to make this recursive at all?
>
> It's only recursive in the formulation; the actual computation is iterative.
>
>   tmp_pic1 = blur_pass(orig_pic, scale_factor);
>   tmp_pic2 = blur_pass(tmp_pic1, (scale_factor - 1.0) / 2.0 + 1.0);
>   tmp_pic3 = blur_pass(tmp_pic2, (scale_factor - 1.0) / 4.0 + 1.0);
>   output_pic = blur_pass(tmp_pic3, (scale_factor - 1.0) / 8.0 + 1.0);
>
> Repeat as many times as desired until you get the quality you want.
>
> You can also be creative with the blend strength if you want a blur that's
> not a box blur.

I fail to see how this can be faster than current approach at same quality.

>
> /* Steinar */
> --
> Homepage: https://www.sesse.net/
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Steinar H. Gunderson July 17, 2020, 9:42 p.m. UTC | #11
On Sun, Jul 12, 2020 at 01:01:07PM +0200, Paul B Mahol wrote:
> +@section rblur
> +Apply Radial blur filter.

I tried this; it looks very aliased and absolutely not like what I'd expect from a
radial blur at all.

  $ wget https://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/PM5644.svg/1000px-PM5644.svg.png
  $ ./ffmpeg -i 1000px-PM5644.svg.png -vf rblur ffmpeg-radialblur.png

produces

  https://storage.sesse.net/ffmpeg-radialblur.png

This is what the GIMP's “Zoom Blur” gives me on the same picture, and what I
expect a radial blur to look like:

  https://storage.sesse.net/gimp-radialblur.png
  
The circular blur looks like the right effect, but it's again very aliased
and has some sort of accuracy problem:

  http://storage.sesse.net/ffmpeg-circularblur.png

Look at the vertical lines in the left part of the picture; they have some
weird kind of staircase effect.

/* Stienar */
Paul B Mahol July 18, 2020, 8:17 a.m. UTC | #12
On 7/17/20, Steinar H. Gunderson <steinar+ffmpeg@gunderson.no> wrote:
> On Sun, Jul 12, 2020 at 01:01:07PM +0200, Paul B Mahol wrote:
>> +@section rblur
>> +Apply Radial blur filter.
>
> I tried this; it looks very aliased and absolutely not like what I'd expect
> from a
> radial blur at all.
>
>   $ wget
> https://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/PM5644.svg/1000px-PM5644.svg.png
>   $ ./ffmpeg -i 1000px-PM5644.svg.png -vf rblur ffmpeg-radialblur.png
>
> produces
>
>   https://storage.sesse.net/ffmpeg-radialblur.png
>
> This is what the GIMP's “Zoom Blur” gives me on the same picture, and what I
> expect a radial blur to look like:
>
>   https://storage.sesse.net/gimp-radialblur.png
>
> The circular blur looks like the right effect, but it's again very aliased
> and has some sort of accuracy problem:
>
>   http://storage.sesse.net/ffmpeg-circularblur.png
>
> Look at the vertical lines in the left part of the picture; they have some
> weird kind of staircase effect.
>

You are deeply confused, filters are working just fine.

> /* Stienar */
> --
> Homepage: https://www.sesse.net/
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Steinar H. Gunderson July 18, 2020, 8:24 a.m. UTC | #13
On Sat, Jul 18, 2020 at 10:17:27AM +0200, Paul B Mahol wrote:
>>   https://storage.sesse.net/ffmpeg-radialblur.png
> You are deeply confused, filters are working just fine.

The above picture pretty clearly shows otherwise...? Could you tell me where
my confusion would lie?

/* Steinar */
Paul B Mahol July 18, 2020, 8:28 a.m. UTC | #14
On 7/18/20, Steinar H. Gunderson <steinar+ffmpeg@gunderson.no> wrote:
> On Sat, Jul 18, 2020 at 10:17:27AM +0200, Paul B Mahol wrote:
>>>   https://storage.sesse.net/ffmpeg-radialblur.png
>> You are deeply confused, filters are working just fine.
>
> The above picture pretty clearly shows otherwise...? Could you tell me where
> my confusion would lie?

The filter option amount/angle set is very small.

>
> /* Steinar */
> --
> Homepage: https://www.sesse.net/
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Steinar H. Gunderson July 18, 2020, 8:29 a.m. UTC | #15
On Sat, Jul 18, 2020 at 10:28:21AM +0200, Paul B Mahol wrote:
>> The above picture pretty clearly shows otherwise...? Could you tell me where
>> my confusion would lie?
> The filter option amount/angle set is very small.

It's the default value. Could you recommend a command line?

/* Steinar */
Paul B Mahol July 18, 2020, 9:06 a.m. UTC | #16
On 7/18/20, Steinar H. Gunderson <steinar+ffmpeg@gunderson.no> wrote:
> On Sat, Jul 18, 2020 at 10:28:21AM +0200, Paul B Mahol wrote:
>>> The above picture pretty clearly shows otherwise...? Could you tell me
>>> where
>>> my confusion would lie?
>> The filter option amount/angle set is very small.
>
> It's the default value. Could you recommend a command line?

Unless you want to propose patch, no.

>
> /* Steinar */
> --
> Homepage: https://www.sesse.net/
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Steinar H. Gunderson July 18, 2020, 10:37 a.m. UTC | #17
On Sat, Jul 18, 2020 at 11:06:17AM +0200, Paul B Mahol wrote:
>>>> The above picture pretty clearly shows otherwise...? Could you tell me
>>>> where my confusion would lie?
>>> The filter option amount/angle set is very small.
>> It's the default value. Could you recommend a command line?
> Unless you want to propose patch, no.

OK, so the defaults create a broken image, and you're not willing to tell me
what parameters to use without me proposing a patch? (What would that patch
do?)

I tried setting angle=50, and got

  http://storage.sesse.net/ffmpeg-radialblur2.png

which looks more like a radial blur, but still is broken. Note the strong
aliasing (starburst) in the center, the jagged thick white lines (more
aliasing) on the left and right sides, and the fact that the vertical white
lines are all but gone.

If a blur creates strong high-frequency components (ie., the very opposite of
what a blur is supposed to do), it is a strong signal that the algorithm
chosen is just fundamentally wrong.

/* Steinar */
Paul B Mahol July 18, 2020, 10:53 a.m. UTC | #18
On 7/18/20, Steinar H. Gunderson <steinar+ffmpeg@gunderson.no> wrote:
> On Sat, Jul 18, 2020 at 11:06:17AM +0200, Paul B Mahol wrote:
>>>>> The above picture pretty clearly shows otherwise...? Could you tell me
>>>>> where my confusion would lie?
>>>> The filter option amount/angle set is very small.
>>> It's the default value. Could you recommend a command line?
>> Unless you want to propose patch, no.
>
> OK, so the defaults create a broken image, and you're not willing to tell me
> what parameters to use without me proposing a patch? (What would that patch
> do?)
>
> I tried setting angle=50, and got
>
>   http://storage.sesse.net/ffmpeg-radialblur2.png
>
> which looks more like a radial blur, but still is broken. Note the strong
> aliasing (starburst) in the center, the jagged thick white lines (more
> aliasing) on the left and right sides, and the fact that the vertical white
> lines are all but gone.
>
> If a blur creates strong high-frequency components (ie., the very opposite
> of
> what a blur is supposed to do), it is a strong signal that the algorithm
> chosen is just fundamentally wrong.

Nope, algorithm is just fine. You are interpreting results wrongly.

>
> /* Steinar */
> --
> Homepage: https://www.sesse.net/
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Steinar H. Gunderson July 18, 2020, 10:58 a.m. UTC | #19
On Sat, Jul 18, 2020 at 12:53:18PM +0200, Paul B Mahol wrote:
> Nope, algorithm is just fine. You are interpreting results wrongly.

If you are not willing to tell me what is wrong with my interpretation
(outside “you are using the wrong parameters, and I won't tell you what the
right parameters are”), this is a hard claim to make.

In short, this patch has:

 - An unusually slow algorithm (sin, cos, atan, division; all per-pixel).
 - Poor output quality (strong aliasing, no subpixel precision).
 - No comments, hardly any commit message.

Thus, I believe it should not be merged.

/* Steinar */
Paul B Mahol July 18, 2020, 11:31 a.m. UTC | #20
On 7/18/20, Steinar H. Gunderson <steinar+ffmpeg@gunderson.no> wrote:
> On Sat, Jul 18, 2020 at 12:53:18PM +0200, Paul B Mahol wrote:
>> Nope, algorithm is just fine. You are interpreting results wrongly.
>
> If you are not willing to tell me what is wrong with my interpretation
> (outside “you are using the wrong parameters, and I won't tell you what the
> right parameters are”), this is a hard claim to make.
>
> In short, this patch has:
>
>  - An unusually slow algorithm (sin, cos, atan, division; all per-pixel).

This is actually good. Performance is not affected at all.

>  - Poor output quality (strong aliasing, no subpixel precision).

I fixed this. No thanks to your patch.

>  - No comments, hardly any commit message.

No comments are needed for developers that know how to code.

>
> Thus, I believe it should not be merged.

It is just your belief, no any proof given.

>
> /* Steinar */
> --
> Homepage: https://www.sesse.net/
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Steinar H. Gunderson July 18, 2020, 2:48 p.m. UTC | #21
On Sat, Jul 18, 2020 at 01:31:49PM +0200, Paul B Mahol wrote:
>> In short, this patch has:
>>
>>  - An unusually slow algorithm (sin, cos, atan, division; all per-pixel).
> This is actually good.

Is it good that it's slow? Or do you mean that the algorithm isn't actually
slow?

> Performance is not affected at all.

It sure is. With -vf vblur, a simple single-threaded transcode (to magicyuv)
drops from 64 to 5 fps. A quick perf run indicates that about 67% of the CPU
time is spent in converting to and from polar coordinates (p2c, c2p, atan2),
give or take a little.

>>  - Poor output quality (strong aliasing, no subpixel precision).
> I fixed this. No thanks to your patch.

It's good that you fixed the quality issue you claimed didn't exist less than
an hour earlier. Could you please post the updated patch?

>>  - No comments, hardly any commit message.
> No comments are needed for developers that know how to code.

The FFmpeg coding style disagrees:

https://ffmpeg.org/developer.html#Comments -- “All nontrivial functions
should have a comment above them explaining what the function does, even if
it is just one sentence. All structures and their member variables should be
documented, too.”

https://ffmpeg.org/developer.html#Patches_002fCommitting -- “Always fill out
the commit log message. Describe in a few lines what you changed and why.”

I don't see any exceptions for “developers that know how to code”; and after
all, also those who are not so good coders should be able to understand the
code.

>> Thus, I believe it should not be merged.
> It is just your belief, no any proof given.

I believe I fairly clearly spelled out my reasoning in the text you quoted?

/* Steinar */
Paul B Mahol July 18, 2020, 2:59 p.m. UTC | #22
On 7/18/20, Steinar H. Gunderson <steinar+ffmpeg@gunderson.no> wrote:
> On Sat, Jul 18, 2020 at 01:31:49PM +0200, Paul B Mahol wrote:
>>> In short, this patch has:
>>>
>>>  - An unusually slow algorithm (sin, cos, atan, division; all per-pixel).
>> This is actually good.
>
> Is it good that it's slow? Or do you mean that the algorithm isn't actually
> slow?
>

It is pretty fast here.


>> Performance is not affected at all.
>
> It sure is. With -vf vblur, a simple single-threaded transcode (to magicyuv)
> drops from 64 to 5 fps. A quick perf run indicates that about 67% of the CPU
> time is spent in converting to and from polar coordinates (p2c, c2p, atan2),
> give or take a little.

It can be made faster, but why i would do that when you complain on
current code?

Actually, fact is that you came here at first to bring some jumbo
about better algorithms
with pages that had almost no valuable information.
And later when proven wrong you started to object to current patch
just for pure amusement.

>
>>>  - Poor output quality (strong aliasing, no subpixel precision).
>> I fixed this. No thanks to your patch.
>
> It's good that you fixed the quality issue you claimed didn't exist less
> than
> an hour earlier. Could you please post the updated patch?

I will keep it to my fork only. You can enjoy your negative reviews
later at any time.

>
>>>  - No comments, hardly any commit message.
>> No comments are needed for developers that know how to code.
>
> The FFmpeg coding style disagrees:
>
> https://ffmpeg.org/developer.html#Comments -- “All nontrivial functions
> should have a comment above them explaining what the function does, even if
> it is just one sentence. All structures and their member variables should be
> documented, too.”
>
> https://ffmpeg.org/developer.html#Patches_002fCommitting -- “Always fill out
> the commit log message. Describe in a few lines what you changed and why.”
>
> I don't see any exceptions for “developers that know how to code”; and after
> all, also those who are not so good coders should be able to understand the
> code.

Code is self explanatory and does not need comments for mortals.

>
>>> Thus, I believe it should not be merged.
>> It is just your belief, no any proof given.
>
> I believe I fairly clearly spelled out my reasoning in the text you quoted?

The reasoning is completely flawed as explained multiple times.

>
> /* Steinar */
> --
> Homepage: https://www.sesse.net/
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Steinar H. Gunderson July 18, 2020, 3:13 p.m. UTC | #23
On Sat, Jul 18, 2020 at 04:59:21PM +0200, Paul B Mahol wrote:
>> Is it good that it's slow? Or do you mean that the algorithm isn't actually
>> slow?
> It is pretty fast here.

Could you quantify what you mean by “pretty fast”? (Is it fast also measured
in CPU time, or only if you have a bunch of cores to spread it out over?)

>> It sure is. With -vf vblur, a simple single-threaded transcode (to magicyuv)
>> drops from 64 to 5 fps. A quick perf run indicates that about 67% of the CPU
>> time is spent in converting to and from polar coordinates (p2c, c2p, atan2),
>> give or take a little.
> It can be made faster, but why i would do that when you complain on
> current code?

I agree that optimizing the current algorithm is not a good way to go
when there are multiple better ones available, but you were claiming
performance was not affected at all, which fairly obviously is not true.

> Actually, fact is that you came here at first to bring some jumbo
> about better algorithms
> with pages that had almost no valuable information.
> And later when proven wrong you started to object to current patch
> just for pure amusement.

Can we please talk about the patch instead of personal attacks?

Also, what do you mean by “proven wrong”? Your only “proof” so far that your
algorithm is better than the standard vector-based variations (whether
recursive, or simply by integrating along a line with bilinear filtering,
neither of which needs any trigonometry or divisions) has been an expression
of doubt -- no benchmarks, no reasoning, no back-of-the-envelope
calculations.

>> It's good that you fixed the quality issue you claimed didn't exist less
>> than an hour earlier. Could you please post the updated patch?
> I will keep it to my fork only. You can enjoy your negative reviews
> later at any time.

OK, if you don't plan to actually commit this to FFmpeg master, then we don't
need to argue further about it.

/* Steinar */
Paul B Mahol July 18, 2020, 3:23 p.m. UTC | #24
On 7/18/20, Steinar H. Gunderson <steinar+ffmpeg@gunderson.no> wrote:
> On Sat, Jul 18, 2020 at 04:59:21PM +0200, Paul B Mahol wrote:
>>> Is it good that it's slow? Or do you mean that the algorithm isn't
>>> actually
>>> slow?
>> It is pretty fast here.
>
> Could you quantify what you mean by “pretty fast”? (Is it fast also measured
> in CPU time, or only if you have a bunch of cores to spread it out over?)

It is faster than many other filters, and could be made even faster.
But considering that I do all this for free, why I should listen to someone who
spends 5 seconds to write review.

>
>>> It sure is. With -vf vblur, a simple single-threaded transcode (to
>>> magicyuv)
>>> drops from 64 to 5 fps. A quick perf run indicates that about 67% of the
>>> CPU
>>> time is spent in converting to and from polar coordinates (p2c, c2p,
>>> atan2),
>>> give or take a little.
>> It can be made faster, but why i would do that when you complain on
>> current code?
>
> I agree that optimizing the current algorithm is not a good way to go
> when there are multiple better ones available, but you were claiming
> performance was not affected at all, which fairly obviously is not true.

Why you still spreading nonsense that there is better algorithm?

>
>> Actually, fact is that you came here at first to bring some jumbo
>> about better algorithms
>> with pages that had almost no valuable information.
>> And later when proven wrong you started to object to current patch
>> just for pure amusement.
>
> Can we please talk about the patch instead of personal attacks?
>
> Also, what do you mean by “proven wrong”? Your only “proof” so far that your
> algorithm is better than the standard vector-based variations (whether
> recursive, or simply by integrating along a line with bilinear filtering,
> neither of which needs any trigonometry or divisions) has been an expression
> of doubt -- no benchmarks, no reasoning, no back-of-the-envelope
> calculations.

There is no better algorithm than current one.
If you want to prove different, write one!
But if you do not want, then better current solution than no solution at all.
It could have even same options like current implementation than just
code would be changed.

>
>>> It's good that you fixed the quality issue you claimed didn't exist less
>>> than an hour earlier. Could you please post the updated patch?
>> I will keep it to my fork only. You can enjoy your negative reviews
>> later at any time.
>
> OK, if you don't plan to actually commit this to FFmpeg master, then we
> don't
> need to argue further about it.

I changed my mind when I looked how many benefits it give.

>
> /* Steinar */
> --
> Homepage: https://www.sesse.net/
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Alexander Strasser July 19, 2020, 9:08 p.m. UTC | #25
Hi all,

this mail thread has turned into an example of exactly what I would
not like to see in a patch discussion!

It doesn't really respect the first sentence of our code of conduct:

    Be friendly and respectful towards others and third parties.


Furthermore it discourages testing, code reviews and discussions on
patch sets, which I think is destructive in its own. Especially having
people compile and test the patches sent to the list, is not something
that happens for every patch set and should normally be welcomed and
appreciated.

From what I feel myself and from what I have read recently on this
list from other people in another thread, these kinds of mails makes
people feel uncomfortable to contribute or to even keep reading this
mailing list.

As I interpret the last email, the patch set was resubmitted and the
discussion is reset now. I hope this can continue in a good way now.


[...]

  Alexander
diff mbox series

Patch

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 */