diff --git a/doc/filters.texi b/doc/filters.texi
index 6ab41706e5..dd6c9f1fee 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -25243,6 +25243,7 @@ ffplay -f lavfi life=s=300x200:mold=10:r=60:ratio=0.1:death_color=#C83232:life_c
 @anchor{allrgb}
 @anchor{allyuv}
 @anchor{color}
+@anchor{colorspectrum}
 @anchor{haldclutsrc}
 @anchor{nullsrc}
 @anchor{pal75bars}
@@ -25253,7 +25254,7 @@ ffplay -f lavfi life=s=300x200:mold=10:r=60:ratio=0.1:death_color=#C83232:life_c
 @anchor{testsrc}
 @anchor{testsrc2}
 @anchor{yuvtestsrc}
-@section allrgb, allyuv, color, haldclutsrc, nullsrc, pal75bars, pal100bars, rgbtestsrc, smptebars, smptehdbars, testsrc, testsrc2, yuvtestsrc
+@section allrgb, allyuv, color, colorspectrum, haldclutsrc, nullsrc, pal75bars, pal100bars, rgbtestsrc, smptebars, smptehdbars, testsrc, testsrc2, yuvtestsrc
 
 The @code{allrgb} source returns frames of size 4096x4096 of all rgb colors.
 
@@ -25261,6 +25262,8 @@ The @code{allyuv} source returns frames of size 4096x4096 of all yuv colors.
 
 The @code{color} source provides an uniformly colored input.
 
+The @code{colorspectrum} source provides a color spectrum input.
+
 The @code{haldclutsrc} source provides an identity Hald CLUT. See also
 @ref{haldclut} filter.
 
@@ -25352,6 +25355,16 @@ Set the number of decimals to show in the timestamp, only available in the
 The displayed timestamp value will correspond to the original
 timestamp value multiplied by the power of 10 of the specified
 value. Default value is 0.
+
+@item type
+Set the type of the color spectrum, only available in the
+@code{colorspectrum} source. Can be one of the following:
+
+@table @samp
+@item black
+@item white
+@item all
+@end table
 @end table
 
 @subsection Examples
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 8c5e565ed1..13c2be1671 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -528,6 +528,7 @@ OBJS-$(CONFIG_ALLRGB_FILTER)                 += vsrc_testsrc.o
 OBJS-$(CONFIG_ALLYUV_FILTER)                 += vsrc_testsrc.o
 OBJS-$(CONFIG_CELLAUTO_FILTER)               += vsrc_cellauto.o
 OBJS-$(CONFIG_COLOR_FILTER)                  += vsrc_testsrc.o
+OBJS-$(CONFIG_COLORSPECTRUM_FILTER)          += vsrc_testsrc.o
 OBJS-$(CONFIG_COREIMAGESRC_FILTER)           += vf_coreimage.o
 OBJS-$(CONFIG_FREI0R_SRC_FILTER)             += vf_frei0r.o
 OBJS-$(CONFIG_GRADIENTS_FILTER)              += vsrc_gradients.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index d7556bc93f..d0d9bbaeed 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -503,6 +503,7 @@ extern const AVFilter ff_vsrc_allrgb;
 extern const AVFilter ff_vsrc_allyuv;
 extern const AVFilter ff_vsrc_cellauto;
 extern const AVFilter ff_vsrc_color;
+extern const AVFilter ff_vsrc_colorspectrum;
 extern const AVFilter ff_vsrc_coreimagesrc;
 extern const AVFilter ff_vsrc_frei0r_src;
 extern const AVFilter ff_vsrc_gradients;
diff --git a/libavfilter/vsrc_testsrc.c b/libavfilter/vsrc_testsrc.c
index 6f48a8f86c..8f5e9d71df 100644
--- a/libavfilter/vsrc_testsrc.c
+++ b/libavfilter/vsrc_testsrc.c
@@ -70,6 +70,9 @@ typedef struct TestSourceContext {
     /* only used by testsrc2 */
     int alpha;
 
+    /* only used by colorspectrum */
+    int type;
+
     /* only used by color */
     FFDrawContext draw;
     FFDrawColor color;
@@ -1791,3 +1794,92 @@ const AVFilter ff_vsrc_allrgb = {
 };
 
 #endif /* CONFIG_ALLRGB_FILTER */
+
+#if CONFIG_ALLRGB_FILTER
+
+static const AVOption colorspectrum_options[] = {
+    COMMON_OPTIONS
+    { "type", "set the color spectrum type", OFFSET(type), AV_OPT_TYPE_INT, {.i64=0}, 0, 2, FLAGS, "type" },
+    { "black","fade to black",               0,            AV_OPT_TYPE_CONST,{.i64=0},0, 0, FLAGS, "type" },
+    { "white","fade to white",               0,            AV_OPT_TYPE_CONST,{.i64=1},0, 0, FLAGS, "type" },
+    { "all",  "white to black",              0,            AV_OPT_TYPE_CONST,{.i64=2},0, 0, FLAGS, "type" },
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(colorspectrum);
+
+static inline float mix(float a, float b, float mix)
+{
+    return a * mix + b * (1.f - mix);
+}
+
+static void hsb2rgb(const float *c, float *rgb)
+{
+    rgb[0] = av_clipf(fabsf(fmodf(c[0] * 6.f + 0.f, 6.f) - 3.f) - 1.f, 0.f, 1.f);
+    rgb[1] = av_clipf(fabsf(fmodf(c[0] * 6.f + 4.f, 6.f) - 3.f) - 1.f, 0.f, 1.f);
+    rgb[2] = av_clipf(fabsf(fmodf(c[0] * 6.f + 2.f, 6.f) - 3.f) - 1.f, 0.f, 1.f);
+    rgb[0] = mix(c[3], (rgb[0] * rgb[0] * (3.f - 2.f * rgb[0])), c[1]) * c[2];
+    rgb[1] = mix(c[3], (rgb[1] * rgb[1] * (3.f - 2.f * rgb[1])), c[1]) * c[2];
+    rgb[2] = mix(c[3], (rgb[2] * rgb[2] * (3.f - 2.f * rgb[2])), c[1]) * c[2];
+}
+
+static void colorspectrum_fill_picture(AVFilterContext *ctx, AVFrame *frame)
+{
+    TestSourceContext *test = ctx->priv;
+    const float w = frame->width - 1.f;
+    const float h = frame->height - 1.f;
+    float c[4];
+
+    for (int y = 0; y < frame->height; y++) {
+        float *r = (float *)(frame->data[2] + y * frame->linesize[2]);
+        float *g = (float *)(frame->data[0] + y * frame->linesize[0]);
+        float *b = (float *)(frame->data[1] + y * frame->linesize[1]);
+        const float yh = y / h;
+
+        c[1] = test->type == 2 ? yh > 0.5f ? 2.f * (yh - 0.5f) : 1.f - 2.f * yh : test->type == 1 ? 1.f - yh : yh;
+        c[2] = 1.f;
+        c[3] = test->type == 1 ? 1.f : test->type == 2 ? (yh > 0.5f ? 0.f : 1.f): 0.f;
+        for (int x = 0; x < frame->width; x++) {
+            float rgb[3];
+
+            c[0] = x / w;
+            hsb2rgb(c, rgb);
+
+            r[x] = rgb[0];
+            g[x] = rgb[1];
+            b[x] = rgb[2];
+        }
+    }
+}
+
+static av_cold int colorspectrum_init(AVFilterContext *ctx)
+{
+    TestSourceContext *test = ctx->priv;
+
+    test->draw_once = 1;
+    test->fill_picture_fn = colorspectrum_fill_picture;
+    return init(ctx);
+}
+
+static const AVFilterPad avfilter_vsrc_colorspectrum_outputs[] = {
+    {
+        .name          = "default",
+        .type          = AVMEDIA_TYPE_VIDEO,
+        .config_props  = config_props,
+    },
+};
+
+const AVFilter ff_vsrc_colorspectrum = {
+    .name          = "colorspectrum",
+    .description   = NULL_IF_CONFIG_SMALL("Generate colors spectrum."),
+    .priv_size     = sizeof(TestSourceContext),
+    .priv_class    = &colorspectrum_class,
+    .init          = colorspectrum_init,
+    .uninit        = uninit,
+    .activate      = activate,
+    .inputs        = NULL,
+    FILTER_OUTPUTS(avfilter_vsrc_colorspectrum_outputs),
+    FILTER_SINGLE_PIXFMT(AV_PIX_FMT_GBRPF32),
+};
+
+#endif /* CONFIG_ALLRGB_FILTER */
