diff mbox series

[FFmpeg-devel] lavfi: add perlin noise generator

Message ID ZlYNNHeNHOG8Z0R1@mariano
State New
Headers show
Series [FFmpeg-devel] lavfi: add perlin noise generator | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Stefano Sabatini May 28, 2024, 4:58 p.m. UTC
On date Monday 2024-05-27 23:37:33 +0200, Stefano Sabatini wrote:
> Hi,
> 
> still missing documentation and might be optimized (and maybe extended
> to support gray16 - this should be simple), comments are welcome.

Updated with documentation.

Comments

Stefano Sabatini June 3, 2024, 9:41 a.m. UTC | #1
On date Tuesday 2024-05-28 18:58:28 +0200, Stefano Sabatini wrote:
> On date Monday 2024-05-27 23:37:33 +0200, Stefano Sabatini wrote:
> > Hi,
> > 
> > still missing documentation and might be optimized (and maybe extended
> > to support gray16 - this should be simple), comments are welcome.
> 
> Updated with documentation.

> From 607459e7a184ab2d111b65f5017fb7f76e3bd58d Mon Sep 17 00:00:00 2001
> From: Stefano Sabatini <stefasab@gmail.com>
> Date: Mon, 27 May 2024 11:19:08 +0200
> Subject: [PATCH] lavfi: add perlin noise generator

Anyone interested with reviewing this?

Otherwise I'll probably push in a week or so, further improvements can
be done on top of that.
Stefano Sabatini June 12, 2024, 7:30 p.m. UTC | #2
On date Monday 2024-06-03 11:41:59 +0200, Stefano Sabatini wrote:
> On date Tuesday 2024-05-28 18:58:28 +0200, Stefano Sabatini wrote:
> > On date Monday 2024-05-27 23:37:33 +0200, Stefano Sabatini wrote:
> > > Hi,
> > > 
> > > still missing documentation and might be optimized (and maybe extended
> > > to support gray16 - this should be simple), comments are welcome.
> > 
> > Updated with documentation.
> 
> > From 607459e7a184ab2d111b65f5017fb7f76e3bd58d Mon Sep 17 00:00:00 2001
> > From: Stefano Sabatini <stefasab@gmail.com>
> > Date: Mon, 27 May 2024 11:19:08 +0200
> > Subject: [PATCH] lavfi: add perlin noise generator
> 
> Anyone interested with reviewing this?
> 
> Otherwise I'll probably push in a week or so, further improvements can
> be done on top of that.

Updated patch with minor doc fixes, will push it in a few days unless
I see comments.
Andrew Sayers June 13, 2024, 3:45 p.m. UTC | #3
Some documentation nitpicks.  Nothing jumped out about the code, but I don't
know the algorithm well enough to spot anything deep.

> From 9932cfc19500acbd0685eb2cc8fd88e9af3f5dbd Mon Sep 17 00:00:00 2001
> From: Stefano Sabatini <stefasab@gmail.com>
> Date: Mon, 27 May 2024 11:19:08 +0200
> Subject: [PATCH] lavfi: add Perlin noise generator
> 
> ---
>  Changelog                 |   1 +
>  doc/filters.texi          | 100 +++++++++++++++++
>  libavfilter/Makefile      |   1 +
>  libavfilter/allfilters.c  |   1 +
>  libavfilter/perlin.c      | 224 ++++++++++++++++++++++++++++++++++++++
>  libavfilter/perlin.h      | 101 +++++++++++++++++
>  libavfilter/vsrc_perlin.c | 169 ++++++++++++++++++++++++++++
>  7 files changed, 597 insertions(+)
>  create mode 100644 libavfilter/perlin.c
>  create mode 100644 libavfilter/perlin.h
>  create mode 100644 libavfilter/vsrc_perlin.c
> 
> diff --git a/Changelog b/Changelog
> index 03d6b29ad8..b8dcf452ac 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -12,6 +12,7 @@ version <next>:
>  - qsv_params option added for QSV encoders
>  - VVC decoder compatible with DVB test content
>  - xHE-AAC decoder
> +- perlin source
>  
>  
>  version 7.0:
> diff --git a/doc/filters.texi b/doc/filters.texi
> index 347103c04f..7af299b2a2 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -17290,6 +17290,9 @@ The command accepts the same syntax of the corresponding option.
>  If the specified expression is not valid, it is kept at its current
>  value.
>  
> +@anchor{lutrgb}
> +@anchor{lutyuv}
> +@anchor{lut}
>  @section lut, lutrgb, lutyuv
>  
>  Compute a look-up table for binding each pixel component input value
> @@ -29281,6 +29284,103 @@ ffplay -f lavfi life=s=300x200:mold=10:r=60:ratio=0.1:death_color=#C83232:life_c
>  @end example
>  @end itemize
>  
> +@section perlin
> +Generate Perlin noise.
> +
> +Perlin noise is a kind of noise with local continuity in space. This
> +can be used to generate patterns with continuity in space and time,
> +e.g. to simulate smoke, fluids, or terrain.
> +
> +In case more than one octave is specified through the @option{octaves}
> +option, Perlin noise is generated as a sum of components, each one
> +with doubled frequency. In this case the @option{persistence} option
> +specify the ratio of the amplitude with respect to the previous
> +component. More octave components enable to specify more high
> +frequency details in the generated noise (e.g. small size variations
> +due to bolders in a generated terrain).

Typo: s/bolders/boulders/

> +
> +@subsection Options
> +@table @option
> +
> +@item size, s
> +Specify the size (width and height) of the buffered video frames. For the
> +syntax of this option, check the
> +@ref{video size syntax,,"Video size" section in the ffmpeg-utils manual,ffmpeg-utils}.
> +
> +@item rate, r
> +Specify the frame rate expected for the video stream, expressed as a
> +number of frames per second.
> +
> +@item octaves
> +Specify the total number of components making up the noise, each one
> +with doubled frequency.
> +
> +@item persistence
> +Set the ratio used to compute the amplitude of the next octave
> +component with respect to the previous component amplitude.
> +
> +@item xscale
> +@item yscale
> +Define a scale factor used to multiple the x, y coordinates. This can
> +be useful to define an effect with a pattern stretched along the x or
> +y axis.
> +
> +@item tscale
> +Define a scale factor used to multiple the time coordinate. This can
> +be useful to change the time variation speed.
> +
> +@item random_mode
> +Set random mode used to compute initial pattern.
> +
> +Supported values are:
> +@table @option
> +@item random
> +Compute and use random seed.
> +
> +@item ken
> +Use the predefined initial pattern defined by Ken Perlin in the
> +original article, can be useful to compare the output with other
> +sources.
> +
> +@item seed
> +Use the value specified by @option{random_seed} option.
> +@end table

Nit: "Define a...", "Use the..." etc. is redundant - remove them to
optimise for reading time.

> +
> +@item random_seed, seed
> +Use this value to compute the initial pattern, it is only considered
> +when @option{random_mode} is set to @var{random_seed}.
> +@end table

Nit: the reader needs to read the second clause before the first makes sense.
Consider something like:

When @option{random_mode} is set to @var{random_seed},
this value computes the initial pattern.

> +
> +@subsection Examples
> +@itemize
> +@item
> +Generate single component:
> +@example
> +perlin
> +@end example
> +
> +@item
> +Use Perlin noise with 7 components, each one with a halved contribute

s/contribute/contribution/

> +to total amplitude:
> +@example
> +perlin=octaves=7:persistence=0.5
> +@end example
> +
> +@item
> +Chain Perlin noise with the @ref{lutyuv} to generate a black&white
> +effect:
> +@example
> +perlin:octaves=7:tscale=0.3,lutyuv=y='if(lt(val\,128)\,255\,0)'
> +@end example
> +
> +@item
> +Stretch noise along the y axis, and convert gray level to red-only

I initially thought this was a typo for "read-only" - maybe s/red/blue/?

> +signal:
> +@example
> +perlin=octaves=7:tscale=0.4:yscale=0.3,lutrgb=r=val:b=0:g=0
> +@end example
> +@end itemize
> +
>  @section qrencodesrc
>  
>  Generate a QR code using the libqrencode library (see
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index 5992fd161f..63088e9286 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -603,6 +603,7 @@ OBJS-$(CONFIG_NULLSRC_FILTER)                += vsrc_testsrc.o
>  OBJS-$(CONFIG_OPENCLSRC_FILTER)              += vf_program_opencl.o opencl.o
>  OBJS-$(CONFIG_PAL75BARS_FILTER)              += vsrc_testsrc.o
>  OBJS-$(CONFIG_PAL100BARS_FILTER)             += vsrc_testsrc.o
> +OBJS-$(CONFIG_PERLIN_FILTER)                 += vsrc_perlin.o perlin.o
>  OBJS-$(CONFIG_QRENCODE_FILTER)               += qrencode.o textutils.o
>  OBJS-$(CONFIG_QRENCODESRC_FILTER)            += qrencode.o textutils.o
>  OBJS-$(CONFIG_RGBTESTSRC_FILTER)             += vsrc_testsrc.o
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index c532682fc2..63600e9b58 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -569,6 +569,7 @@ extern const AVFilter ff_vsrc_openclsrc;
>  extern const AVFilter ff_vsrc_qrencodesrc;
>  extern const AVFilter ff_vsrc_pal75bars;
>  extern const AVFilter ff_vsrc_pal100bars;
> +extern const AVFilter ff_vsrc_perlin;
>  extern const AVFilter ff_vsrc_rgbtestsrc;
>  extern const AVFilter ff_vsrc_sierpinski;
>  extern const AVFilter ff_vsrc_smptebars;
> diff --git a/libavfilter/perlin.c b/libavfilter/perlin.c
> new file mode 100644
> index 0000000000..f09020bf8f
> --- /dev/null
> +++ b/libavfilter/perlin.c
> @@ -0,0 +1,224 @@
> +/*
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 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 General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with FFmpeg; if not, write to the Free Software Foundation, Inc.,
> + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
> + */
> +
> +/**
> + * @file
> + * Perlin Noise generator, based on code from:
> + * https://adrianb.io/2014/08/09/perlinnoise.html
> + *
> + * Original article from Ken Perlin:
> + * http://mrl.nyu.edu/~perlin/paper445.pdf
> + */
> +
> +#include <math.h>
> +
> +#include "libavutil/lfg.h"
> +#include "libavutil/random_seed.h"
> +#include "perlin.h"
> +
> +static inline int inc(int num, int period)
> +{
> +    num++;
> +    if (period > 0)
> +        num %= period;
> +    return num;
> +}
> +
> +static inline double grad(int hash, double x, double y, double z)
> +{
> +    // Take the hashed value and take the first 4 bits of it (15 == 0b1111)
> +    int h = hash & 15;
> +    // If the most significant bit (MSB) of the hash is 0 then set u = x.  Otherwise y.
> +    double u = h < 8 /* 0b1000 */ ? x : y;
> +    double v;
> +
> +    // In Ken Perlin's original implementation this was another
> +    // conditional operator (?:), then expanded for readability.
> +    if (h < 4 /* 0b0100 */)
> +        // If the first and second significant bits are 0 set v = y
> +        v = y;
> +    // If the first and second significant bits are 1 set v = x
> +    else if (h == 12 /* 0b1100 */ || h == 14 /* 0b1110*/)

Nit: add space before final *

> +        v = x;
> +    else
> +        // If the first and second significant bits are not equal (0/1, 1/0) set v = z
> +        v = z;
> +
> +    // Use the last 2 bits to decide if u and v are positive or negative.  Then return their addition.
> +    return ((h&1) == 0 ? u : -u)+((h&2) == 0 ? v : -v);
> +}
> +
> +static inline double fade(double t)
> +{
> +    // Fade function as defined by Ken Perlin. This eases coordinate values
> +    // so that they will "ease" towards integral values. This ends up smoothing
> +    // the final output.

Given the verbosity of the comments so far, do you want to explicitly mention
how this is using Horner's method?

https://en.wikipedia.org/wiki/Horner%27s_method

> +    return t * t * t * (t * (t * 6 - 15) + 10);                     // 6t^5 - 15t^4 + 10t^3
> +}
> +
> +static double lerp(double a, double b, double x)
> +{
> +    return a + x * (b - a);
> +}
> +
> +// Hash lookup table as defined by Ken Perlin.  This is a randomly
> +// arranged array of all numbers from 0-255 inclusive.
> +static uint8_t ken_permutations[] = {
> +    151, 160, 137,  91,  90,  15, 131,  13, 201,  95,  96,  53, 194, 233,   7, 225,
> +    140,  36, 103,  30,  69, 142,   8,  99,  37, 240,  21,  10,  23, 190,   6, 148,
> +    247, 120, 234,  75,   0,  26, 197,  62,  94, 252, 219, 203, 117,  35,  11,  32,
> +     57, 177,  33,  88, 237, 149,  56,  87, 174,  20, 125, 136, 171, 168,  68, 175,
> +     74, 165,  71, 134, 139,  48,  27, 166,  77, 146, 158, 231,  83, 111, 229, 122,
> +     60, 211, 133, 230, 220, 105,  92,  41,  55,  46, 245,  40, 244, 102, 143,  54,
> +     65,  25,  63, 161,   1, 216,  80,  73, 209,  76, 132, 187, 208,  89,  18, 169,
> +    200, 196, 135, 130, 116, 188, 159,  86, 164, 100, 109, 198, 173, 186,   3,  64,
> +     52, 217, 226, 250, 124, 123,   5, 202,  38, 147, 118, 126, 255,  82,  85, 212,
> +    207, 206,  59, 227,  47,  16,  58,  17, 182, 189,  28,  42, 223, 183, 170, 213,
> +    119, 248, 152,   2,  44, 154, 163,  70, 221, 153, 101, 155, 167,  43, 172,   9,
> +    129,  22,  39, 253,  19,  98, 108, 110,  79, 113, 224, 232, 178, 185, 112, 104,
> +    218, 246,  97, 228, 251,  34, 242, 193, 238, 210, 144,  12, 191, 179, 162, 241,
> +     81,  51, 145, 235, 249,  14, 239, 107,  49, 192, 214,  31, 181, 199, 106, 157,
> +    184,  84, 204, 176, 115, 121,  50,  45, 127,   4, 150, 254, 138, 236, 205,  93,
> +    222, 114,  67,  29,  24,  72, 243, 141, 128, 195,  78,  66, 215,  61, 156, 180
> +};
> +
> +int ff_perlin_init(FFPerlin *perlin, double period, int octaves, double persistence,
> +                   enum FFPerlinRandomMode random_mode, unsigned int random_seed)
> +{
> +    int i;
> +
> +    /* todo: perform validation? */

Should this line be removed?

> +    perlin->period = period;
> +    perlin->octaves = octaves;
> +    perlin->persistence = persistence;
> +    perlin->random_mode = random_mode;
> +    perlin->random_seed = random_seed;
> +
> +    if (perlin->random_mode == FF_PERLIN_RANDOM_MODE_KEN) {
> +        for (i = 0; i < 512; i++) {
> +            perlin->permutations[i] = ken_permutations[i % 256];
> +        }
> +    } else {
> +        AVLFG lfg;
> +        uint8_t random_permutations[256];
> +
> +        if (perlin->random_mode == FF_PERLIN_RANDOM_MODE_RANDOM)
> +            perlin->random_seed = av_get_random_seed();
> +
> +        av_lfg_init(&lfg, perlin->random_seed);
> +
> +        for (i = 0; i < 256; i++) {
> +            random_permutations[i] = i;
> +        }
> +
> +        for (i = 0; i < 256; i++) {
> +            unsigned int random_idx = av_lfg_get(&lfg) % (256-i);
> +            uint8_t random_val = random_permutations[random_idx];
> +            random_permutations[random_idx] = random_permutations[256-i];
> +
> +            perlin->permutations[i] = perlin->permutations[i+256] = random_val;
> +        }
> +    }
> +
> +    return 0;
> +}
> +
> +static double perlin_get(FFPerlin *perlin, double x, double y, double z)
> +{
> +    int xi, yi, zi;
> +    double xf, yf, zf;
> +    double u, v, w;
> +    const uint8_t *p = perlin->permutations;
> +    double period = perlin->period;
> +    int aaa, aba, aab, abb, baa, bba, bab, bbb;
> +    double x1, x2, y1, y2;
> +
> +    if (perlin->period > 0) {
> +        // If we have any period on, change the coordinates to their "local" repetitions
> +        x = fmod(x, perlin->period);
> +        y = fmod(y, perlin->period);
> +        z = fmod(z, perlin->period);
> +    }
> +
> +    // Calculate the "unit cube" that the point asked will be located in
> +    // The left bound is ( |_x_|,|_y_|,|_z_| ) and the right bound is that
> +    // plus 1.  Next we calculate the location (from 0.0 to 1.0) in that cube.
> +    xi = (int)x & 255;
> +    yi = (int)y & 255;
> +    zi = (int)z & 255;
> +
> +    xf = x - (int)x;
> +    yf = y - (int)y;
> +    zf = z - (int)z;
> +
> +    // We also fade the location to smooth the result.
> +    u = fade(xf);
> +    v = fade(yf);
> +    w = fade(zf);
> +
> +    aaa = p[p[p[    xi         ] +     yi         ] +     zi         ];
> +    aba = p[p[p[    xi         ] + inc(yi, period)] +     zi         ];
> +    aab = p[p[p[    xi         ] +     yi         ] + inc(zi, period)];
> +    abb = p[p[p[    xi         ] + inc(yi, period)] + inc(zi, period)];
> +    baa = p[p[p[inc(xi, period)] +     yi         ] +     zi         ];
> +    bba = p[p[p[inc(xi, period)] + inc(yi, period)] +     zi         ];
> +    bab = p[p[p[inc(xi, period)] +     yi         ] + inc(zi, period)];
> +    bbb = p[p[p[inc(xi, period)] + inc(yi, period)] + inc(zi, period)];
> +
> +    // The gradient function calculates the dot product between a pseudorandom
> +    // gradient vector and the vector from the input coordinate to the 8
> +    // surrounding points in its unit cube.
> +    // This is all then lerped together as a sort of weighted average based on the faded (u,v,w)
> +    // values we made earlier.
> +    x1 = lerp(grad(aaa, xf  , yf  , zf),
> +              grad(baa, xf-1, yf  , zf),
> +              u);
> +    x2 = lerp(grad(aba, xf  , yf-1, zf),
> +              grad(bba, xf-1, yf-1, zf),
> +              u);
> +    y1 = lerp(x1, x2, v);
> +
> +    x1 = lerp(grad(aab, xf  , yf  , zf-1),
> +              grad(bab, xf-1, yf  , zf-1),
> +              u);
> +    x2 = lerp(grad(abb, xf  , yf-1, zf-1),
> +              grad(bbb, xf-1, yf-1, zf-1),
> +                    u);
> +    y2 = lerp(x1, x2, v);
> +
> +    // For convenience we bound it to 0 - 1 (theoretical min/max before is -1 - 1)
> +    return (lerp(y1, y2, w) + 1) / 2;
> +}
> +
> +double ff_perlin_get(FFPerlin *perlin, double x, double y, double z)
> +{
> +    double total = 0;
> +    double frequency = 1;
> +    double amplitude = 1;
> +    double max_value = 0;                   // Used for normalizing result to 0.0 - 1.0
> +
> +    for (int i = 0; i < perlin->octaves; i++) {
> +        total += perlin_get(perlin, x * frequency, y * frequency, z * frequency) * amplitude;
> +        max_value += amplitude;
> +        amplitude *= perlin->persistence;
> +        frequency *= 2;
> +    }
> +
> +    return total / max_value;
> +}
> +
> diff --git a/libavfilter/perlin.h b/libavfilter/perlin.h
> new file mode 100644
> index 0000000000..1d2922a587
> --- /dev/null
> +++ b/libavfilter/perlin.h
> @@ -0,0 +1,101 @@
> +/*
> + * Perlin noise generator
> + *
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +
> +/**
> + * @file
> + * Perlin Noise generator
> + */
> +
> +#ifndef AVFILTER_PERLIN_H
> +#define AVFILTER_PERLIN_H
> +
> +#include <stdint.h>
> +
> +enum FFPerlinRandomMode {
> +    FF_PERLIN_RANDOM_MODE_RANDOM,
> +    FF_PERLIN_RANDOM_MODE_KEN,
> +    FF_PERLIN_RANDOM_MODE_SEED,
> +    FF_PERLIN_RANDOM_MODE_NB
> +};
> +
> +/**
> + * Perlin generator context. This needs to be initialized with the
> + * parameters used to generate the Perlin noise.
> + */
> +typedef struct FFPerlin {
> +    /**
> +     * spatial repeat period, if negative it is ignored
> +     */
> +    double period;
> +
> +    /**
> +     * total number of components making up the noise, each one with
> +     * doubled frequency
> +     */
> +    int octaves;
> +
> +    /**
> +     * ratio used to compute the amplitude of the next octave
> +     * component with respect to the previous component
> +     */
> +    double persistence;
> +
> +    /**
> +     * permutations array used to compute the Perlin noise hash
> +     */
> +    uint8_t permutations[512];
> +
> +    /**
> +     * define how to compute the permutations array
> +     */
> +    enum FFPerlinRandomMode random_mode;
> +
> +    /**
> +     * random seed used to compute the permutations array when random_mode is set to
> +     * FF_PERLIN_RANDOM_MODE_RANDOM,

Nit: remove trailing comma

> +     */
> +    unsigned int random_seed;
> +} FFPerlin;
> +
> +/**
> + * Initialize the Perlin noise generator with parameters.
> + *
> + * @param perlin Perlin noise generator context
> + * @param period spatial repeat period, if negative it is ignored
> + * @param octaves total number of components making up the noise, each one with doubled frequency
> + * @param persistence define how to ratio used to compute the amplitude of the next
> + *                    octave component with respect to the previous component

I think "how to ratio used" should be "how the ratio is used"?

> + * @param random_mode define how to compute the permutations array
> + * @param random_seed random seed used to compute the permutations array when random_mode is set to
> + *                    FF_PERLIN_RANDOM_MODE_RANDOM,

Nit: trailing comma again

> + * @return a negative AVERROR code in case of error, a non negative value otherwise
> + */
> +int ff_perlin_init(FFPerlin *perlin, double period, int octaves, double persistence,
> +                   enum FFPerlinRandomMode random_mode, unsigned int random_seed);
> +
> +/**
> + * Compute Perlin noise for the 3D coordinates given the x, y, z coordinates.

Nit: "for the 3D coordinates" and "given the x, y, z coordinates" is redundant -
either is fine, but not both.

> + *
> + * @param perlin Perlin noise generator context
> + * @return normalized value for the perlin noise, in the range [0, 1]
> + */
> +double ff_perlin_get(FFPerlin *perlin, double x, double y, double z);
> +
> +#endif  /* AVFILTER_PERLIN_H */
> diff --git a/libavfilter/vsrc_perlin.c b/libavfilter/vsrc_perlin.c
> new file mode 100644
> index 0000000000..27493a1b3b
> --- /dev/null
> +++ b/libavfilter/vsrc_perlin.c
> @@ -0,0 +1,169 @@
> +/*
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +
> +/**
> + * @file
> + * Perlin noise generator
> + */
> +
> +#include <float.h>
> +
> +#include "perlin.h"
> +#include "libavutil/lfg.h"
> +#include "libavutil/opt.h"
> +#include "avfilter.h"
> +#include "internal.h"
> +#include "formats.h"
> +#include "video.h"
> +
> +typedef struct PerlinContext {
> +    const AVClass *class;
> +
> +    int w, h;
> +    AVRational frame_rate;
> +
> +    FFPerlin perlin;
> +    int octaves;
> +    double persistence;
> +    unsigned int random_seed;
> +    enum FFPerlinRandomMode random_mode;
> +
> +    double xscale, yscale, tscale;
> +    uint64_t pts;
> +} PerlinContext;
> +
> +#define OFFSET(x) offsetof(PerlinContext, x)
> +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
> +
> +static const AVOption perlin_options[] = {
> +    { "size",     "set video size",   OFFSET(w),        AV_OPT_TYPE_IMAGE_SIZE, {.str = "320x240"}, 0, 0, FLAGS },
> +    { "s",        "set video size",   OFFSET(w),        AV_OPT_TYPE_IMAGE_SIZE, {.str = "320x240"}, 0, 0, FLAGS },
> +    { "rate",     "set video rate",   OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, INT_MAX, FLAGS },
> +    { "r",        "set video rate",   OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, INT_MAX, FLAGS },
> +    { "octaves", "set the number of components to use to generate the noise", OFFSET(octaves), AV_OPT_TYPE_INT, {.i64=1}, 1, INT_MAX, FLAGS },
> +    { "persistence", "set the octaves persistence", OFFSET(persistence), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0.0, DBL_MAX, FLAGS },
> +
> +    { "xscale", "set x-scale factor", OFFSET(xscale), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0.0, DBL_MAX, FLAGS },
> +    { "yscale", "set y-scale factor", OFFSET(yscale), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0.0, DBL_MAX, FLAGS },
> +    { "tscale", "set t-scale factor", OFFSET(tscale), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0.0, DBL_MAX, FLAGS },
> +
> +    { "random_mode", "set random mode used to compute initial pattern", OFFSET(random_mode), AV_OPT_TYPE_INT, {.i64=FF_PERLIN_RANDOM_MODE_RANDOM}, 0, FF_PERLIN_RANDOM_MODE_NB-1, FLAGS, .unit = "random_mode" },
> +    { "random", "compute and use random seed", 0, AV_OPT_TYPE_CONST, {.i64=FF_PERLIN_RANDOM_MODE_RANDOM},   0, 0, FLAGS, .unit = "random_mode" },
> +    { "ken", "use the predefined initial pattern defined by Ken Perlin in the original article", 0, AV_OPT_TYPE_CONST, {.i64=FF_PERLIN_RANDOM_MODE_KEN}, 0, 0, FLAGS, .unit = "random_mode" },
> +    { "seed", "use the value specified by random_seed", 0, AV_OPT_TYPE_CONST, {.i64=FF_PERLIN_RANDOM_MODE_SEED}, 0, 0, FLAGS, .unit = "random_mode" },
> +
> +    { "random_seed", "set the seed for filling the initial grid randomly", OFFSET(random_seed), AV_OPT_TYPE_UINT, {.i64=0}, 0, UINT_MAX, FLAGS },
> +    { "seed",        "set the seed for filling the initial grid randomly", OFFSET(random_seed), AV_OPT_TYPE_UINT, {.i64=0}, 0, UINT_MAX, FLAGS },

Nit: "set the seed for filling the initial grid randomly" is ambiguous.
Do you mean:

a) Set the seed for the RNG to a specified value (value is the seed number)
b) Pick a random number to fill the grid with (value is a boolean yes/no)

I settled on the former after several reads, but would appreciate clearer wording.

> +    { NULL }
> +};
> +
> +AVFILTER_DEFINE_CLASS(perlin);
> +
> +static av_cold int init(AVFilterContext *ctx)
> +{
> +    PerlinContext *perlin = ctx->priv;
> +    int ret;
> +
> +    if (ret = ff_perlin_init(&perlin->perlin, -1, perlin->octaves, perlin->persistence,
> +                             perlin->random_mode, perlin->random_seed)) {
> +        return ret;
> +    }
> +
> +    av_log(ctx, AV_LOG_VERBOSE,
> +           "s:%dx%d r:%d/%d octaves:%d persistence:%f xscale:%f yscale:%f tscale:%f\n",
> +           perlin->w, perlin->h, perlin->frame_rate.num, perlin->frame_rate.den,
> +           perlin->octaves, perlin->persistence,
> +           perlin->xscale, perlin->yscale, perlin->tscale);
> +    return 0;
> +}
> +
> +static int config_props(AVFilterLink *outlink)
> +{
> +    PerlinContext *perlin = outlink->src->priv;
> +
> +    outlink->w = perlin->w;
> +    outlink->h = perlin->h;
> +    outlink->time_base = av_inv_q(perlin->frame_rate);
> +    outlink->frame_rate = perlin->frame_rate;
> +
> +    return 0;
> +}
> +
> +static int request_frame(AVFilterLink *outlink)
> +{
> +    AVFilterContext *ctx = outlink->src;
> +    PerlinContext *perlin = ctx->priv;
> +    AVFrame *picref = ff_get_video_buffer(outlink, perlin->w, perlin->h);
> +    int i, j;
> +    uint8_t *data0, *data;
> +    double x, y, t;
> +
> +    if (!picref)
> +        return AVERROR(ENOMEM);
> +
> +    picref->sample_aspect_ratio = (AVRational) {1, 1};
> +    picref->pts = perlin->pts++;
> +    picref->duration = 1;
> +
> +    t = perlin->tscale * (perlin->pts * av_q2d(outlink->time_base));
> +    data0 = picref->data[0];
> +
> +    for (i = 0; i < perlin->h; i++) {
> +        y = perlin->yscale * (double)i / perlin->h;
> +
> +        data = data0;
> +
> +        for (j = 0; j < perlin->w; j++) {
> +            double res;
> +            x = perlin->xscale * (double)j / perlin->w;
> +            res = ff_perlin_get(&perlin->perlin, x, y, t);
> +            av_log(ctx, AV_LOG_DEBUG, "x:%f y:%f t:%f => %f\n", x, y, t, res);
> +            *data++ = res * 255;
> +        }
> +        data0 += picref->linesize[0];
> +    }
> +
> +    return ff_filter_frame(outlink, picref);
> +}
> +
> +static int query_formats(AVFilterContext *ctx)
> +{
> +    enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE };
> +
> +    return ff_set_common_formats_from_list(ctx, pix_fmts);
> +}
> +
> +static const AVFilterPad perlin_outputs[] = {
> +    {
> +        .name          = "default",
> +        .type          = AVMEDIA_TYPE_VIDEO,
> +        .request_frame = request_frame,
> +        .config_props  = config_props,
> +    },
> +};
> +
> +const AVFilter ff_vsrc_perlin = {
> +    .name          = "perlin",
> +    .description   = NULL_IF_CONFIG_SMALL("Generate Perlin noise"),
> +    .priv_size     = sizeof(PerlinContext),
> +    .priv_class    = &perlin_class,
> +    .init          = init,
> +    .inputs        = NULL,
> +    FILTER_OUTPUTS(perlin_outputs),
> +    FILTER_QUERY_FUNC(query_formats),
> +};
> -- 
> 2.34.1
> 

> _______________________________________________
> 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".
Stefano Sabatini June 16, 2024, 3:37 p.m. UTC | #4
On date Thursday 2024-06-13 16:45:48 +0100, Andrew Sayers wrote:
> Some documentation nitpicks.  Nothing jumped out about the code, but I don't
> know the algorithm well enough to spot anything deep.
> 
> > From 9932cfc19500acbd0685eb2cc8fd88e9af3f5dbd Mon Sep 17 00:00:00 2001
> > From: Stefano Sabatini <stefasab@gmail.com>
> > Date: Mon, 27 May 2024 11:19:08 +0200
> > Subject: [PATCH] lavfi: add Perlin noise generator
> > 
> > ---
> >  Changelog                 |   1 +
> >  doc/filters.texi          | 100 +++++++++++++++++
> >  libavfilter/Makefile      |   1 +
> >  libavfilter/allfilters.c  |   1 +
> >  libavfilter/perlin.c      | 224 ++++++++++++++++++++++++++++++++++++++
> >  libavfilter/perlin.h      | 101 +++++++++++++++++
> >  libavfilter/vsrc_perlin.c | 169 ++++++++++++++++++++++++++++
> >  7 files changed, 597 insertions(+)
> >  create mode 100644 libavfilter/perlin.c
> >  create mode 100644 libavfilter/perlin.h
> >  create mode 100644 libavfilter/vsrc_perlin.c
[...] 
> > +@item tscale
> > +Define a scale factor used to multiple the time coordinate. This can
> > +be useful to change the time variation speed.
> > +
> > +@item random_mode
> > +Set random mode used to compute initial pattern.
> > +
> > +Supported values are:
> > +@table @option
> > +@item random
> > +Compute and use random seed.
> > +
> > +@item ken
> > +Use the predefined initial pattern defined by Ken Perlin in the
> > +original article, can be useful to compare the output with other
> > +sources.
> > +
> > +@item seed
> > +Use the value specified by @option{random_seed} option.
> > +@end table
> 
> Nit: "Define a...", "Use the..." etc. is redundant - remove them to
> optimise for reading time.

kept current form since the following is a complete sentence

> > +@item
> > +Chain Perlin noise with the @ref{lutyuv} to generate a black&white
> > +effect:
> > +@example
> > +perlin:octaves=7:tscale=0.3,lutyuv=y='if(lt(val\,128)\,255\,0)'
> > +@end example
> > +
> > +@item
> > +Stretch noise along the y axis, and convert gray level to red-only
> 

> I initially thought this was a typo for "read-only" - maybe s/red/blue/?

I prefer to keep red, this is a fire approximation

[...]
> 
> > +    { "random_mode", "set random mode used to compute initial pattern", OFFSET(random_mode), AV_OPT_TYPE_INT, {.i64=FF_PERLIN_RANDOM_MODE_RANDOM}, 0, FF_PERLIN_RANDOM_MODE_NB-1, FLAGS, .unit = "random_mode" },
> > +    { "random", "compute and use random seed", 0, AV_OPT_TYPE_CONST, {.i64=FF_PERLIN_RANDOM_MODE_RANDOM},   0, 0, FLAGS, .unit = "random_mode" },
> > +    { "ken", "use the predefined initial pattern defined by Ken Perlin in the original article", 0, AV_OPT_TYPE_CONST, {.i64=FF_PERLIN_RANDOM_MODE_KEN}, 0, 0, FLAGS, .unit = "random_mode" },
> > +    { "seed", "use the value specified by random_seed", 0, AV_OPT_TYPE_CONST, {.i64=FF_PERLIN_RANDOM_MODE_SEED}, 0, 0, FLAGS, .unit = "random_mode" },
> > +
> > +    { "random_seed", "set the seed for filling the initial grid randomly", OFFSET(random_seed), AV_OPT_TYPE_UINT, {.i64=0}, 0, UINT_MAX, FLAGS },
> > +    { "seed",        "set the seed for filling the initial grid randomly", OFFSET(random_seed), AV_OPT_TYPE_UINT, {.i64=0}, 0, UINT_MAX, FLAGS },
> 
> Nit: "set the seed for filling the initial grid randomly" is ambiguous.
> Do you mean:
> 
> a) Set the seed for the RNG to a specified value (value is the seed number)
> b) Pick a random number to fill the grid with (value is a boolean yes/no)
> 
> I settled on the former after several reads, but would appreciate clearer wording.

Changed to:
set the seed for filling the initial pattern

[...]

Applied other fixes as well, thanks.
Stefano Sabatini July 1, 2024, 8:34 p.m. UTC | #5
On date Sunday 2024-06-16 17:37:09 +0200, Stefano Sabatini wrote:
> On date Thursday 2024-06-13 16:45:48 +0100, Andrew Sayers wrote:
[...]
> Applied other fixes as well, thanks.

> From 7d9ffbdb8e419ca01e60604038d5534a3ca8ae0e Mon Sep 17 00:00:00 2001
> From: Stefano Sabatini <stefasab@gmail.com>
> Date: Mon, 27 May 2024 11:19:08 +0200
> Subject: [PATCH] lavfi: add Perlin noise generator
> 
> ---
>  Changelog                 |   1 +
>  doc/filters.texi          | 100 +++++++++++++++++
>  libavfilter/Makefile      |   1 +
>  libavfilter/allfilters.c  |   1 +
>  libavfilter/perlin.c      | 224 ++++++++++++++++++++++++++++++++++++++
>  libavfilter/perlin.h      | 101 +++++++++++++++++
>  libavfilter/vsrc_perlin.c | 169 ++++++++++++++++++++++++++++
>  7 files changed, 597 insertions(+)
>  create mode 100644 libavfilter/perlin.c
>  create mode 100644 libavfilter/perlin.h
>  create mode 100644 libavfilter/vsrc_perlin.c

Applied.
Paul B Mahol July 2, 2024, 8:17 a.m. UTC | #6
No need for query function.
This implementation is so blatantly slow and inefficient that is not useful.
Source filters should use .activate instead.
Many more other issues...

On Mon, Jul 1, 2024 at 11:52 PM Stefano Sabatini <stefasab@gmail.com> wrote:

> On date Sunday 2024-06-16 17:37:09 +0200, Stefano Sabatini wrote:
> > On date Thursday 2024-06-13 16:45:48 +0100, Andrew Sayers wrote:
> [...]
> > Applied other fixes as well, thanks.
>
> > From 7d9ffbdb8e419ca01e60604038d5534a3ca8ae0e Mon Sep 17 00:00:00 2001
> > From: Stefano Sabatini <stefasab@gmail.com>
> > Date: Mon, 27 May 2024 11:19:08 +0200
> > Subject: [PATCH] lavfi: add Perlin noise generator
> >
> > ---
> >  Changelog                 |   1 +
> >  doc/filters.texi          | 100 +++++++++++++++++
> >  libavfilter/Makefile      |   1 +
> >  libavfilter/allfilters.c  |   1 +
> >  libavfilter/perlin.c      | 224 ++++++++++++++++++++++++++++++++++++++
> >  libavfilter/perlin.h      | 101 +++++++++++++++++
> >  libavfilter/vsrc_perlin.c | 169 ++++++++++++++++++++++++++++
> >  7 files changed, 597 insertions(+)
> >  create mode 100644 libavfilter/perlin.c
> >  create mode 100644 libavfilter/perlin.h
> >  create mode 100644 libavfilter/vsrc_perlin.c
>
> Applied.
> _______________________________________________
> 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".
>
Michael Koch July 4, 2024, 3:29 p.m. UTC | #7
Tested and working fine. It would be nice if you could add the default 
values to the documentation.
Stefano Sabatini July 6, 2024, 9:18 a.m. UTC | #8
On date Tuesday 2024-07-02 10:17:26 +0200, Paul B Mahol wrote:
> No need for query function.
> This implementation is so blatantly slow and inefficient that is not useful.
> Source filters should use .activate instead.
> Many more other issues...

It was pending reviews for more than two weeks.

About the lack of optimizations, I'm aware of it but wanted to keep
the initial commit as close as possible to the original
implementation, and didn't had time to work on it yet.

Also, patches are welcome of course.
Stefano Sabatini July 6, 2024, 9:19 a.m. UTC | #9
On date Thursday 2024-07-04 17:29:16 +0200, Michael Koch wrote:
> Tested and working fine. It would be nice if you could add the default
> values to the documentation.

Will do, thanks.
Paul B Mahol July 6, 2024, 10:40 a.m. UTC | #10
On Sat, Jul 6, 2024 at 11:19 AM Stefano Sabatini <stefasab@gmail.com> wrote:

> On date Tuesday 2024-07-02 10:17:26 +0200, Paul B Mahol wrote:
> > No need for query function.
> > This implementation is so blatantly slow and inefficient that is not
> useful.
> > Source filters should use .activate instead.
> > Many more other issues...
>
> It was pending reviews for more than two weeks.
>
> About the lack of optimizations, I'm aware of it but wanted to keep
> the initial commit as close as possible to the original
> implementation, and didn't had time to work on it yet.
>
> Also, patches are welcome of course.
>

Not interested in your marginal toys or project.


> _______________________________________________
> 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".
>
Stefano Sabatini July 6, 2024, 11 a.m. UTC | #11
On date Saturday 2024-07-06 12:40:55 +0200, Paul B Mahol wrote:
> On Sat, Jul 6, 2024 at 11:19 AM Stefano Sabatini <stefasab@gmail.com> wrote:
[...]
> Not interested in your marginal toys or project.

Good, so the next time avoid the rant and save even more time.
Stefano Sabatini July 6, 2024, 1:37 p.m. UTC | #12
On date Tuesday 2024-07-02 10:17:26 +0200, Paul B Mahol wrote:
[...]
> Source filters should use .activate instead.

Can someone elaborate about why .activate is favoured in this case
(sorry I missed the activate discussion altogether and I cannot graps
it from the docs)?

Also, is the direct AVFilterPad API to be considered deprecated in
favor of .activate?
Nicolas George July 7, 2024, 4:36 p.m. UTC | #13
Stefano Sabatini (12024-07-06):
> Can someone elaborate about why .activate is favoured in this case
> (sorry I missed the activate discussion altogether and I cannot graps
> it from the docs)?

I do not know why Paul believes that source filters need to use
activate. It should not be necessary, but it is possible a small bit in
the common framework is missing to fully work with the simplified
interface.

Regards,
diff mbox series

Patch

From 607459e7a184ab2d111b65f5017fb7f76e3bd58d Mon Sep 17 00:00:00 2001
From: Stefano Sabatini <stefasab@gmail.com>
Date: Mon, 27 May 2024 11:19:08 +0200
Subject: [PATCH] lavfi: add perlin noise generator

---
 Changelog                 |   1 +
 doc/filters.texi          | 100 +++++++++++++++++
 libavfilter/Makefile      |   1 +
 libavfilter/allfilters.c  |   1 +
 libavfilter/perlin.c      | 224 ++++++++++++++++++++++++++++++++++++++
 libavfilter/perlin.h      | 100 +++++++++++++++++
 libavfilter/vsrc_perlin.c | 169 ++++++++++++++++++++++++++++
 7 files changed, 596 insertions(+)
 create mode 100644 libavfilter/perlin.c
 create mode 100644 libavfilter/perlin.h
 create mode 100644 libavfilter/vsrc_perlin.c

diff --git a/Changelog b/Changelog
index 12770e4296..1670038d00 100644
--- a/Changelog
+++ b/Changelog
@@ -11,6 +11,7 @@  version <next>:
 - vf_scale2ref deprecated
 - qsv_params option added for QSV encoders
 - VVC decoder compatible with DVB test content
+- perlin source
 
 
 version 7.0:
diff --git a/doc/filters.texi b/doc/filters.texi
index f5bf475d13..baed8c5530 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -17290,6 +17290,9 @@  The command accepts the same syntax of the corresponding option.
 If the specified expression is not valid, it is kept at its current
 value.
 
+@anchor{lutrgb}
+@anchor{lutyuv}
+@anchor{lut}
 @section lut, lutrgb, lutyuv
 
 Compute a look-up table for binding each pixel component input value
@@ -29280,6 +29283,103 @@  ffplay -f lavfi life=s=300x200:mold=10:r=60:ratio=0.1:death_color=#C83232:life_c
 @end example
 @end itemize
 
+@section perlin
+Generate Perlin noise.
+
+Perlin noise is a kind of noise with local continuity in space. This
+can be used to generate patterns with continuity in space and time,
+e.g. to simulate smoke, fluids, or terrain.
+
+In case more than one octave is specified through the @option{octaves}
+option, Perlin noise is generated as a sum of components, each one
+with doubled frequency. In this case the @option{persistence} option
+specify the ratio of the amplitude with respect to the previous
+component. More octave components enable to specify more high
+frequency details in the generated noise (e.g. small size variations
+due to bolders in a generated terrain).
+
+@subsection Options
+@table @option
+
+@item size, s
+Specify the size (width and height) of the buffered video frames. For the
+syntax of this option, check the
+@ref{video size syntax,,"Video size" section in the ffmpeg-utils manual,ffmpeg-utils}.
+
+@item rate, r
+Specify the frame rate expected for the video stream, expressed as a
+number of frames per second.
+
+@item octaves
+Specify the total number of components making up the noise, each one
+with doubled frequency.
+
+@item persistence
+Set the ratio used to compute the amplitude of the next octave
+component with respect to the previous component amplitude.
+
+@item xscale
+@item yscale
+Define a scale factor used to multiple the x, y coordinates. This can
+be useful to define an effect with a pattern stretched along the x or
+y axis.
+
+@item tscale
+Define a scale factor used to multiple the time coordinate. This can
+be useful to change the time variation speed.
+
+@item random_mode
+Set random mode used to compute initial pattern.
+
+Supported values are:
+@table @option
+@item random
+Compute and use random seed.
+
+@item ken
+Use the predefined initial pattern defined by Ken Perlin in the
+original article, can be useful to compare the output with other
+sources.
+
+@item seed
+Use the value specified by @option{random_seed} option.
+@end table
+
+@item random_seed, seed
+Use this value to compute the initial pattern, it is only considered
+when @option{random_mode} is set to @var{random_seed}.
+@end table
+
+@subsection Examples
+@itemize
+@item
+Generate single component:
+@example
+perlin
+@end example
+
+@item
+Use Perlin noise with 7 components, each one with a halved contribute
+to total amplitude:
+@example
+perlin=octaves=7:persistence=0.5
+@end example
+
+@item 
+Chain Perlin noise with the @ref{lutyuv} to generate a black&white
+effect:
+@example
+perlin:octaves=7:tscale=0.3,lutyuv=y='if(lt(val\,128)\,255\,0)'
+@end example
+
+@item
+Stretch noise along the y axis, and convert gray level to red-only
+signal:
+@example
+perlin=octaves=7:tscale=0.4:yscale=0.3,lutrgb=r=val:b=0:g=0
+@end example
+@end itemize
+
 @section qrencodesrc
 
 Generate a QR code using the libqrencode library (see
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 5992fd161f..63088e9286 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -603,6 +603,7 @@  OBJS-$(CONFIG_NULLSRC_FILTER)                += vsrc_testsrc.o
 OBJS-$(CONFIG_OPENCLSRC_FILTER)              += vf_program_opencl.o opencl.o
 OBJS-$(CONFIG_PAL75BARS_FILTER)              += vsrc_testsrc.o
 OBJS-$(CONFIG_PAL100BARS_FILTER)             += vsrc_testsrc.o
+OBJS-$(CONFIG_PERLIN_FILTER)                 += vsrc_perlin.o perlin.o
 OBJS-$(CONFIG_QRENCODE_FILTER)               += qrencode.o textutils.o
 OBJS-$(CONFIG_QRENCODESRC_FILTER)            += qrencode.o textutils.o
 OBJS-$(CONFIG_RGBTESTSRC_FILTER)             += vsrc_testsrc.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index c532682fc2..63600e9b58 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -569,6 +569,7 @@  extern const AVFilter ff_vsrc_openclsrc;
 extern const AVFilter ff_vsrc_qrencodesrc;
 extern const AVFilter ff_vsrc_pal75bars;
 extern const AVFilter ff_vsrc_pal100bars;
+extern const AVFilter ff_vsrc_perlin;
 extern const AVFilter ff_vsrc_rgbtestsrc;
 extern const AVFilter ff_vsrc_sierpinski;
 extern const AVFilter ff_vsrc_smptebars;
diff --git a/libavfilter/perlin.c b/libavfilter/perlin.c
new file mode 100644
index 0000000000..f09020bf8f
--- /dev/null
+++ b/libavfilter/perlin.c
@@ -0,0 +1,224 @@ 
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with FFmpeg; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * @file
+ * Perlin Noise generator, based on code from:
+ * https://adrianb.io/2014/08/09/perlinnoise.html
+ *
+ * Original article from Ken Perlin:
+ * http://mrl.nyu.edu/~perlin/paper445.pdf
+ */
+
+#include <math.h>
+
+#include "libavutil/lfg.h"
+#include "libavutil/random_seed.h"
+#include "perlin.h"
+
+static inline int inc(int num, int period)
+{
+    num++;
+    if (period > 0)
+        num %= period;
+    return num;
+}
+
+static inline double grad(int hash, double x, double y, double z)
+{
+    // Take the hashed value and take the first 4 bits of it (15 == 0b1111)
+    int h = hash & 15;
+    // If the most significant bit (MSB) of the hash is 0 then set u = x.  Otherwise y.
+    double u = h < 8 /* 0b1000 */ ? x : y;
+    double v;
+
+    // In Ken Perlin's original implementation this was another
+    // conditional operator (?:), then expanded for readability.
+    if (h < 4 /* 0b0100 */)
+        // If the first and second significant bits are 0 set v = y
+        v = y;
+    // If the first and second significant bits are 1 set v = x
+    else if (h == 12 /* 0b1100 */ || h == 14 /* 0b1110*/)
+        v = x;
+    else
+        // If the first and second significant bits are not equal (0/1, 1/0) set v = z
+        v = z;
+
+    // Use the last 2 bits to decide if u and v are positive or negative.  Then return their addition.
+    return ((h&1) == 0 ? u : -u)+((h&2) == 0 ? v : -v);
+}
+
+static inline double fade(double t)
+{
+    // Fade function as defined by Ken Perlin. This eases coordinate values
+    // so that they will "ease" towards integral values. This ends up smoothing
+    // the final output.
+    return t * t * t * (t * (t * 6 - 15) + 10);                     // 6t^5 - 15t^4 + 10t^3
+}
+
+static double lerp(double a, double b, double x)
+{
+    return a + x * (b - a);
+}
+
+// Hash lookup table as defined by Ken Perlin.  This is a randomly
+// arranged array of all numbers from 0-255 inclusive.
+static uint8_t ken_permutations[] = {
+    151, 160, 137,  91,  90,  15, 131,  13, 201,  95,  96,  53, 194, 233,   7, 225,
+    140,  36, 103,  30,  69, 142,   8,  99,  37, 240,  21,  10,  23, 190,   6, 148,
+    247, 120, 234,  75,   0,  26, 197,  62,  94, 252, 219, 203, 117,  35,  11,  32,
+     57, 177,  33,  88, 237, 149,  56,  87, 174,  20, 125, 136, 171, 168,  68, 175,
+     74, 165,  71, 134, 139,  48,  27, 166,  77, 146, 158, 231,  83, 111, 229, 122,
+     60, 211, 133, 230, 220, 105,  92,  41,  55,  46, 245,  40, 244, 102, 143,  54,
+     65,  25,  63, 161,   1, 216,  80,  73, 209,  76, 132, 187, 208,  89,  18, 169,
+    200, 196, 135, 130, 116, 188, 159,  86, 164, 100, 109, 198, 173, 186,   3,  64,
+     52, 217, 226, 250, 124, 123,   5, 202,  38, 147, 118, 126, 255,  82,  85, 212,
+    207, 206,  59, 227,  47,  16,  58,  17, 182, 189,  28,  42, 223, 183, 170, 213,
+    119, 248, 152,   2,  44, 154, 163,  70, 221, 153, 101, 155, 167,  43, 172,   9,
+    129,  22,  39, 253,  19,  98, 108, 110,  79, 113, 224, 232, 178, 185, 112, 104,
+    218, 246,  97, 228, 251,  34, 242, 193, 238, 210, 144,  12, 191, 179, 162, 241,
+     81,  51, 145, 235, 249,  14, 239, 107,  49, 192, 214,  31, 181, 199, 106, 157,
+    184,  84, 204, 176, 115, 121,  50,  45, 127,   4, 150, 254, 138, 236, 205,  93,
+    222, 114,  67,  29,  24,  72, 243, 141, 128, 195,  78,  66, 215,  61, 156, 180
+};
+
+int ff_perlin_init(FFPerlin *perlin, double period, int octaves, double persistence,
+                   enum FFPerlinRandomMode random_mode, unsigned int random_seed)
+{
+    int i;
+
+    /* todo: perform validation? */
+    perlin->period = period;
+    perlin->octaves = octaves;
+    perlin->persistence = persistence;
+    perlin->random_mode = random_mode;
+    perlin->random_seed = random_seed;
+
+    if (perlin->random_mode == FF_PERLIN_RANDOM_MODE_KEN) {
+        for (i = 0; i < 512; i++) {
+            perlin->permutations[i] = ken_permutations[i % 256];
+        }
+    } else {
+        AVLFG lfg;
+        uint8_t random_permutations[256];
+
+        if (perlin->random_mode == FF_PERLIN_RANDOM_MODE_RANDOM)
+            perlin->random_seed = av_get_random_seed();
+
+        av_lfg_init(&lfg, perlin->random_seed);
+
+        for (i = 0; i < 256; i++) {
+            random_permutations[i] = i;
+        }
+
+        for (i = 0; i < 256; i++) {
+            unsigned int random_idx = av_lfg_get(&lfg) % (256-i);
+            uint8_t random_val = random_permutations[random_idx];
+            random_permutations[random_idx] = random_permutations[256-i];
+
+            perlin->permutations[i] = perlin->permutations[i+256] = random_val;
+        }
+    }
+
+    return 0;
+}
+
+static double perlin_get(FFPerlin *perlin, double x, double y, double z)
+{
+    int xi, yi, zi;
+    double xf, yf, zf;
+    double u, v, w;
+    const uint8_t *p = perlin->permutations;
+    double period = perlin->period;
+    int aaa, aba, aab, abb, baa, bba, bab, bbb;
+    double x1, x2, y1, y2;
+
+    if (perlin->period > 0) {
+        // If we have any period on, change the coordinates to their "local" repetitions
+        x = fmod(x, perlin->period);
+        y = fmod(y, perlin->period);
+        z = fmod(z, perlin->period);
+    }
+
+    // Calculate the "unit cube" that the point asked will be located in
+    // The left bound is ( |_x_|,|_y_|,|_z_| ) and the right bound is that
+    // plus 1.  Next we calculate the location (from 0.0 to 1.0) in that cube.
+    xi = (int)x & 255;
+    yi = (int)y & 255;
+    zi = (int)z & 255;
+
+    xf = x - (int)x;
+    yf = y - (int)y;
+    zf = z - (int)z;
+
+    // We also fade the location to smooth the result.
+    u = fade(xf);
+    v = fade(yf);
+    w = fade(zf);
+
+    aaa = p[p[p[    xi         ] +     yi         ] +     zi         ];
+    aba = p[p[p[    xi         ] + inc(yi, period)] +     zi         ];
+    aab = p[p[p[    xi         ] +     yi         ] + inc(zi, period)];
+    abb = p[p[p[    xi         ] + inc(yi, period)] + inc(zi, period)];
+    baa = p[p[p[inc(xi, period)] +     yi         ] +     zi         ];
+    bba = p[p[p[inc(xi, period)] + inc(yi, period)] +     zi         ];
+    bab = p[p[p[inc(xi, period)] +     yi         ] + inc(zi, period)];
+    bbb = p[p[p[inc(xi, period)] + inc(yi, period)] + inc(zi, period)];
+
+    // The gradient function calculates the dot product between a pseudorandom
+    // gradient vector and the vector from the input coordinate to the 8
+    // surrounding points in its unit cube.
+    // This is all then lerped together as a sort of weighted average based on the faded (u,v,w)
+    // values we made earlier.
+    x1 = lerp(grad(aaa, xf  , yf  , zf),
+              grad(baa, xf-1, yf  , zf),
+              u);
+    x2 = lerp(grad(aba, xf  , yf-1, zf),
+              grad(bba, xf-1, yf-1, zf),
+              u);
+    y1 = lerp(x1, x2, v);
+
+    x1 = lerp(grad(aab, xf  , yf  , zf-1),
+              grad(bab, xf-1, yf  , zf-1),
+              u);
+    x2 = lerp(grad(abb, xf  , yf-1, zf-1),
+              grad(bbb, xf-1, yf-1, zf-1),
+                    u);
+    y2 = lerp(x1, x2, v);
+
+    // For convenience we bound it to 0 - 1 (theoretical min/max before is -1 - 1)
+    return (lerp(y1, y2, w) + 1) / 2;
+}
+
+double ff_perlin_get(FFPerlin *perlin, double x, double y, double z)
+{
+    double total = 0;
+    double frequency = 1;
+    double amplitude = 1;
+    double max_value = 0;                   // Used for normalizing result to 0.0 - 1.0
+
+    for (int i = 0; i < perlin->octaves; i++) {
+        total += perlin_get(perlin, x * frequency, y * frequency, z * frequency) * amplitude;
+        max_value += amplitude;
+        amplitude *= perlin->persistence;
+        frequency *= 2;
+    }
+
+    return total / max_value;
+}
+
diff --git a/libavfilter/perlin.h b/libavfilter/perlin.h
new file mode 100644
index 0000000000..e33e2a0371
--- /dev/null
+++ b/libavfilter/perlin.h
@@ -0,0 +1,100 @@ 
+/*
+ * Perlin noise generator
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * Perlin Noise generator
+ */
+
+#ifndef AVFILTER_PERLIN_H
+#define AVFILTER_PERLIN_H
+
+#include <stdint.h>
+
+enum FFPerlinRandomMode {
+    FF_PERLIN_RANDOM_MODE_RANDOM,
+    FF_PERLIN_RANDOM_MODE_KEN,
+    FF_PERLIN_RANDOM_MODE_SEED,
+    FF_PERLIN_RANDOM_MODE_NB
+};
+
+/**
+ * Perlin generator context. This needs to be initialized with the
+ * parameters used to generate the Perlin noise.
+ */
+typedef struct FFPerlin {
+    /**
+     * spatial repeat period, if negative it is ignored
+     */
+    double period;
+
+    /**
+     * total number of components making up the noise, each one with doubled frequency
+     */
+    int octaves;
+
+    /**
+     * ratio used to compute the amplitude of the next octave
+     * component with respect to the previous component amplitude
+     */
+    double persistence;
+
+    /**
+     * permutations array used to compute the Perlin noise hash
+     */
+    uint8_t permutations[512];
+
+    /**
+     * define how to compute the permutations array
+     */
+    enum FFPerlinRandomMode random_mode;
+
+    /**
+     * random seed used to compute the permutations array when random_mode is set to
+     * FF_PERLIN_RANDOM_MODE_RANDOM,
+     */
+    unsigned int random_seed;
+} FFPerlin;
+
+/**
+ * Initialize the Perlin noise generator with parameters.
+ *
+ * @param perlin Perlin noise generator context
+ * @param period spatial repeat period, if negative it is ignored
+ * @param octaves total number of components making up the noise, each one with doubled frequency
+ * @param persistence define how to ratio used to compute the amplitude of the next
+ *                    octave component with respect to the previous component amplitude
+ * @param random_mode define how to compute the permutations array
+ * @param random_seed random seed used to compute the permutations array when random_mode is set to
+ *                    FF_PERLIN_RANDOM_MODE_RANDOM,
+ * @return a negative AVERROR code in case of error, a non negative value otherwise
+ */
+int ff_perlin_init(FFPerlin *perlin, double period, int octaves, double persistence,
+                   enum FFPerlinRandomMode random_mode, unsigned int random_seed);
+
+/**
+ * Compute Perlin noise for the 3D coordinates given the x, y, z coordinates.
+ *
+ * @param perlin Perlin noise generator context
+ * @return normalized value for the perlin noise, in the range [0, 1]
+ */
+double ff_perlin_get(FFPerlin *perlin, double x, double y, double z);
+
+#endif  /* AVFILTER_PERLIN_H */
diff --git a/libavfilter/vsrc_perlin.c b/libavfilter/vsrc_perlin.c
new file mode 100644
index 0000000000..27493a1b3b
--- /dev/null
+++ b/libavfilter/vsrc_perlin.c
@@ -0,0 +1,169 @@ 
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * Perlin noise generator
+ */
+
+#include <float.h>
+
+#include "perlin.h"
+#include "libavutil/lfg.h"
+#include "libavutil/opt.h"
+#include "avfilter.h"
+#include "internal.h"
+#include "formats.h"
+#include "video.h"
+
+typedef struct PerlinContext {
+    const AVClass *class;
+
+    int w, h;
+    AVRational frame_rate;
+
+    FFPerlin perlin;
+    int octaves;
+    double persistence;
+    unsigned int random_seed;
+    enum FFPerlinRandomMode random_mode;
+
+    double xscale, yscale, tscale;
+    uint64_t pts;
+} PerlinContext;
+
+#define OFFSET(x) offsetof(PerlinContext, x)
+#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
+
+static const AVOption perlin_options[] = {
+    { "size",     "set video size",   OFFSET(w),        AV_OPT_TYPE_IMAGE_SIZE, {.str = "320x240"}, 0, 0, FLAGS },
+    { "s",        "set video size",   OFFSET(w),        AV_OPT_TYPE_IMAGE_SIZE, {.str = "320x240"}, 0, 0, FLAGS },
+    { "rate",     "set video rate",   OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, INT_MAX, FLAGS },
+    { "r",        "set video rate",   OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, INT_MAX, FLAGS },
+    { "octaves", "set the number of components to use to generate the noise", OFFSET(octaves), AV_OPT_TYPE_INT, {.i64=1}, 1, INT_MAX, FLAGS },
+    { "persistence", "set the octaves persistence", OFFSET(persistence), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0.0, DBL_MAX, FLAGS },
+
+    { "xscale", "set x-scale factor", OFFSET(xscale), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0.0, DBL_MAX, FLAGS },
+    { "yscale", "set y-scale factor", OFFSET(yscale), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0.0, DBL_MAX, FLAGS },
+    { "tscale", "set t-scale factor", OFFSET(tscale), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0.0, DBL_MAX, FLAGS },
+
+    { "random_mode", "set random mode used to compute initial pattern", OFFSET(random_mode), AV_OPT_TYPE_INT, {.i64=FF_PERLIN_RANDOM_MODE_RANDOM}, 0, FF_PERLIN_RANDOM_MODE_NB-1, FLAGS, .unit = "random_mode" },
+    { "random", "compute and use random seed", 0, AV_OPT_TYPE_CONST, {.i64=FF_PERLIN_RANDOM_MODE_RANDOM},   0, 0, FLAGS, .unit = "random_mode" },
+    { "ken", "use the predefined initial pattern defined by Ken Perlin in the original article", 0, AV_OPT_TYPE_CONST, {.i64=FF_PERLIN_RANDOM_MODE_KEN}, 0, 0, FLAGS, .unit = "random_mode" },
+    { "seed", "use the value specified by random_seed", 0, AV_OPT_TYPE_CONST, {.i64=FF_PERLIN_RANDOM_MODE_SEED}, 0, 0, FLAGS, .unit = "random_mode" },
+
+    { "random_seed", "set the seed for filling the initial grid randomly", OFFSET(random_seed), AV_OPT_TYPE_UINT, {.i64=0}, 0, UINT_MAX, FLAGS },
+    { "seed",        "set the seed for filling the initial grid randomly", OFFSET(random_seed), AV_OPT_TYPE_UINT, {.i64=0}, 0, UINT_MAX, FLAGS },
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(perlin);
+
+static av_cold int init(AVFilterContext *ctx)
+{
+    PerlinContext *perlin = ctx->priv;
+    int ret;
+
+    if (ret = ff_perlin_init(&perlin->perlin, -1, perlin->octaves, perlin->persistence,
+                             perlin->random_mode, perlin->random_seed)) {
+        return ret;
+    }
+
+    av_log(ctx, AV_LOG_VERBOSE,
+           "s:%dx%d r:%d/%d octaves:%d persistence:%f xscale:%f yscale:%f tscale:%f\n",
+           perlin->w, perlin->h, perlin->frame_rate.num, perlin->frame_rate.den,
+           perlin->octaves, perlin->persistence,
+           perlin->xscale, perlin->yscale, perlin->tscale);
+    return 0;
+}
+
+static int config_props(AVFilterLink *outlink)
+{
+    PerlinContext *perlin = outlink->src->priv;
+
+    outlink->w = perlin->w;
+    outlink->h = perlin->h;
+    outlink->time_base = av_inv_q(perlin->frame_rate);
+    outlink->frame_rate = perlin->frame_rate;
+
+    return 0;
+}
+
+static int request_frame(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    PerlinContext *perlin = ctx->priv;
+    AVFrame *picref = ff_get_video_buffer(outlink, perlin->w, perlin->h);
+    int i, j;
+    uint8_t *data0, *data;
+    double x, y, t;
+
+    if (!picref)
+        return AVERROR(ENOMEM);
+
+    picref->sample_aspect_ratio = (AVRational) {1, 1};
+    picref->pts = perlin->pts++;
+    picref->duration = 1;
+
+    t = perlin->tscale * (perlin->pts * av_q2d(outlink->time_base));
+    data0 = picref->data[0];
+
+    for (i = 0; i < perlin->h; i++) {
+        y = perlin->yscale * (double)i / perlin->h;
+
+        data = data0;
+
+        for (j = 0; j < perlin->w; j++) {
+            double res;
+            x = perlin->xscale * (double)j / perlin->w;
+            res = ff_perlin_get(&perlin->perlin, x, y, t);
+            av_log(ctx, AV_LOG_DEBUG, "x:%f y:%f t:%f => %f\n", x, y, t, res);
+            *data++ = res * 255;
+        }
+        data0 += picref->linesize[0];
+    }
+
+    return ff_filter_frame(outlink, picref);
+}
+
+static int query_formats(AVFilterContext *ctx)
+{
+    enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE };
+
+    return ff_set_common_formats_from_list(ctx, pix_fmts);
+}
+
+static const AVFilterPad perlin_outputs[] = {
+    {
+        .name          = "default",
+        .type          = AVMEDIA_TYPE_VIDEO,
+        .request_frame = request_frame,
+        .config_props  = config_props,
+    },
+};
+
+const AVFilter ff_vsrc_perlin = {
+    .name          = "perlin",
+    .description   = NULL_IF_CONFIG_SMALL("Generate Perlin noise"),
+    .priv_size     = sizeof(PerlinContext),
+    .priv_class    = &perlin_class,
+    .init          = init,
+    .inputs        = NULL,
+    FILTER_OUTPUTS(perlin_outputs),
+    FILTER_QUERY_FUNC(query_formats),
+};
-- 
2.34.1