Commit 734743bb89 for ffmpeg

commit 734743bb89e158727bf0fa0f7a7fd2dd163ce40f
Author: Michael Niedermayer <michael@niedermayer.cc>
Date:   Tue Oct 27 01:38:33 2026 +0100

    avfilter/vf_vignette: add a decimated transform option to the wavelet mode

    A level works in place on the lowpass lattice of the previous one, so
    the mode needs 5 planes instead of 9 + 3 * levels and about an order of
    magnitude less computation. Shrinkage is less effective without the
    shift invariance of the undecimated transform.

    Assisted-by: Claude

diff --git a/doc/filters.texi b/doc/filters.texi
index e46b5a627f..8757bc30a3 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -26391,9 +26391,9 @@ also be used to create a burning effect.

 @item wavelet
 Like @samp{backward}, but the noise amplified in the dark areas is suppressed
-in an undecimated 9/7 wavelet domain: detail coefficients are shrunk toward 0
-according to the local attenuation and the @option{noise} level, while the
-low frequencies, which average over larger areas, are kept.
+in a 9/7 wavelet domain, undecimated by default: detail coefficients are
+shrunk toward 0 according to the local attenuation and the @option{noise}
+level, while the low frequencies, which average over larger areas, are kept.
 It needs about 150 bytes of memory per pixel.
 @end table

@@ -26404,6 +26404,11 @@ Set the standard deviation of the input noise in 8-bit levels, used by the
 @samp{wavelet} mode. Quantization alone contributes about 0.3-0.4.
 Default is @code{0.5}.

+@item decimated
+Use a decimated instead of an undecimated wavelet transform in the
+@samp{wavelet} mode. Several times faster and needs about 20 bytes of memory
+per pixel, the result has slightly more artifacts. Default is @code{0}.
+
 @item eval
 Set evaluation mode for the expressions (@option{angle}, @option{x0}, @option{y0}).

diff --git a/libavfilter/vf_vignette.c b/libavfilter/vf_vignette.c
index a74ebe94d5..9028e6f1db 100644
--- a/libavfilter/vf_vignette.c
+++ b/libavfilter/vf_vignette.c
@@ -70,6 +70,7 @@ typedef struct VignetteContext {
     int mode;                           ///< Mode
     int eval_mode;                      ///< EvalMode
     double noise;
+    int decimated;
     double sqcoeff[2][5];
     float *wplanes;
     size_t wplane_size;
@@ -105,6 +106,7 @@ static const AVOption vignette_options[] = {
     { "dither", "set dithering", OFFSET(do_dither), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS },
     { "aspect", "set aspect ratio", OFFSET(aspect), AV_OPT_TYPE_RATIONAL, {.dbl = 1}, 0, DBL_MAX, .flags = FLAGS },
     { "noise", "set input noise standard deviation for wavelet mode", OFFSET(noise), AV_OPT_TYPE_DOUBLE, {.dbl = 0.5}, 0.01, 255, FLAGS },
+    { "decimated", "use a decimated wavelet transform, faster but lower quality", OFFSET(decimated), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, FLAGS },
     { NULL }
 };

@@ -234,6 +236,8 @@ static int wavelet_levels(int w, int h)
 enum PassType {
     PASS_DECOMPOSE,
     PASS_COMPOSE,
+    PASS_DECOMPOSE_DECIMATED,
+    PASS_COMPOSE_DECIMATED,
     PASS_BOX,
     PASS_ENERGY,
     PASS_GAIN,
@@ -326,6 +330,14 @@ static int run_pass_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_job
         ff_wavelet97_compose_lines(p->dst[0], p->src[0], p->src[1],
                                    l.xstride, l.ystride, p->step, l.w, line0, line1);
         break;
+    case PASS_DECOMPOSE_DECIMATED:
+        ff_wavelet97_decompose_decimated_lines(p->dst[0], p->src[0], p->coeff,
+                                               l.xstride, l.ystride, l.w, line0, line1);
+        break;
+    case PASS_COMPOSE_DECIMATED:
+        ff_wavelet97_compose_decimated_lines(p->dst[0], p->src[0],
+                                             l.xstride, l.ystride, l.w, line0, line1);
+        break;
     case PASS_BOX:    box_lines(p, line0, line1);    break;
     case PASS_ENERGY: energy_lines(p, line0, line1); break;
     case PASS_GAIN:   gain_lines(p, line0, line1);   break;
@@ -366,6 +378,27 @@ static void compose(AVFilterContext *ctx, float *dst, float *const src[4], float
     run_pass(ctx, &p);
 }

+static void decompose_decimated(AVFilterContext *ctx, float *plane, float *temp,
+                                const double coeff[2][5], Lattice lat)
+{
+    WaveletPass p = { PASS_DECOMPOSE_DECIMATED, lat, { temp }, { plane }, coeff };
+
+    run_pass(ctx, &p);
+    p.lat = transpose(lat);
+    p.dst[0] = plane; p.src[0] = temp;
+    run_pass(ctx, &p);
+}
+
+static void compose_decimated(AVFilterContext *ctx, float *plane, float *temp, Lattice lat)
+{
+    WaveletPass p = { PASS_COMPOSE_DECIMATED, transpose(lat), { temp }, { plane } };
+
+    run_pass(ctx, &p);
+    p.lat = lat;
+    p.dst[0] = plane; p.src[0] = temp;
+    run_pass(ctx, &p);
+}
+
 static void box_filter(AVFilterContext *ctx, float *plane, float *temp, int step, Lattice lat)
 {
     WaveletPass p = { PASS_BOX, lat, { temp }, { plane }, NULL, step };
@@ -393,7 +426,7 @@ static void shrink_band(AVFilterContext *ctx, float *band, const float *var, flo
     run_pass(ctx, &p);
 }

-static void transform(AVFilterContext *ctx, float *cur, float *var, int levels, Lattice lat)
+static void transform_undecimated(AVFilterContext *ctx, float *cur, float *var, int levels, Lattice lat)
 {
     VignetteContext *s = ctx->priv;
     const size_t plane_size = s->wplane_size;
@@ -425,6 +458,40 @@ static void transform(AVFilterContext *ctx, float *cur, float *var, int levels,
     }
 }

+/**
+ * Lattice of the lowpass coefficients of the given level of a decimated transform
+ */
+static Lattice level_lattice(Lattice lat, int level)
+{
+    return (Lattice){ lat.xstride << level, lat.ystride << level,
+                      AV_CEIL_RSHIFT(lat.w, level), AV_CEIL_RSHIFT(lat.h, level) };
+}
+
+static void transform_decimated(AVFilterContext *ctx, float *cur, float *var, int levels, Lattice lat)
+{
+    VignetteContext *s = ctx->priv;
+    const size_t plane_size = s->wplane_size;
+    float *temp[3] = { var + plane_size, var + 2 * plane_size, var + 3 * plane_size };
+
+    for (int i = 0; i < levels; i++) {
+        const Lattice level = level_lattice(lat, i);
+
+        decompose_decimated(ctx, cur, temp[0], ff_wavelet97_coeff, level);
+        decompose_decimated(ctx, var, temp[0], s->sqcoeff,         level);
+        for (int j = 1; j < 4; j++) {
+            const int offset = (j & 1) * level.xstride + (j >> 1) * level.ystride;
+            const Lattice band = { 2 * level.xstride, 2 * level.ystride,
+                                   (level.w + !(j & 1)) / 2, (level.h + !(j >> 1)) / 2 };
+            float *scratch[3] = { temp[0] + offset, temp[1] + offset, temp[2] + offset };
+
+            shrink_band(ctx, cur + offset, var + offset, scratch, 1, band);
+        }
+    }
+
+    for (int i = levels - 1; i >= 0; i--)
+        compose_decimated(ctx, cur, temp[0], level_lattice(lat, i));
+}
+
 static void filter_plane_wavelet(AVFilterContext *ctx,
                                  uint8_t *dst, int dst_linesize,
                                  const uint8_t *src, int src_linesize, int pixel_step,
@@ -446,7 +513,10 @@ static void filter_plane_wavelet(AVFilterContext *ctx,
         }
     }

-    transform(ctx, cur, var, wavelet_levels(w, h), lat);
+    if (s->decimated)
+        transform_decimated(ctx, cur, var, wavelet_levels(w, h), lat);
+    else
+        transform_undecimated(ctx, cur, var, wavelet_levels(w, h), lat);

     for (int y = 0; y < h; y++)
         for (int x = 0; x < w; x++)
@@ -597,7 +667,7 @@ static int config_props(AVFilterLink *inlink)

     if (s->mode == MODE_WAVELET) {
         s->wplane_size = (size_t)s->fmap_linesize * inlink->h;
-        s->wplanes = av_malloc_array(s->wplane_size, (9 + 3 * wavelet_levels(inlink->w, inlink->h)) * sizeof(*s->wplanes));
+        s->wplanes = av_malloc_array(s->wplane_size, (s->decimated ? 5 : 9 + 3 * wavelet_levels(inlink->w, inlink->h)) * sizeof(*s->wplanes));
         if (!s->wplanes)
             return AVERROR(ENOMEM);
     }