PR #25502 opened by michaelni
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25502
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25502.patch

on Lenna it improves reversal PSNR by 0.3 to 11.5 dB over mode=backward
Reusing wavelet implementation already in libavfilter supporting slice 
threading.

wavelet could be optimized alot, but thats out of scope, this patch just uses 
the existing code



From fbb3bdbbfd2beea0a9fc4e997d2b97f8805edad5 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Tue, 27 Oct 2026 00:02:57 +0100
Subject: [PATCH 1/6] avfilter/owdenoise: move the undecimated 9/7 wavelet
 transform into wavelet97.c

Makes the transform usable by other filters. The analysis filter taps become
a parameter so the same code can also propagate variances with squared taps.

Assisted-by: Claude
---
 libavfilter/Makefile       |   2 +-
 libavfilter/vf_owdenoise.c | 117 ++------------------------------
 libavfilter/wavelet97.c    | 135 +++++++++++++++++++++++++++++++++++++
 libavfilter/wavelet97.h    |  55 +++++++++++++++
 4 files changed, 196 insertions(+), 113 deletions(-)
 create mode 100644 libavfilter/wavelet97.c
 create mode 100644 libavfilter/wavelet97.h

diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index ef2542b98b9..8669ff192a0 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -432,7 +432,7 @@ OBJS-$(CONFIG_OVERLAY_OPENCL_FILTER)         += 
vf_overlay_opencl.o opencl.o \
 OBJS-$(CONFIG_OVERLAY_QSV_FILTER)            += vf_overlay_qsv.o framesync.o
 OBJS-$(CONFIG_OVERLAY_VAAPI_FILTER)          += vf_overlay_vaapi.o framesync.o 
vaapi_vpp.o
 OBJS-$(CONFIG_OVERLAY_VULKAN_FILTER)         += vf_overlay_vulkan.o vulkan.o 
vulkan_filter.o
-OBJS-$(CONFIG_OWDENOISE_FILTER)              += vf_owdenoise.o
+OBJS-$(CONFIG_OWDENOISE_FILTER)              += vf_owdenoise.o wavelet97.o
 OBJS-$(CONFIG_PAD_FILTER)                    += vf_pad.o
 OBJS-$(CONFIG_PAD_CUDA_FILTER)               += vf_pad_cuda.o 
vf_pad_cuda.ptx.o cuda/load_helper.o
 OBJS-$(CONFIG_PAD_OPENCL_FILTER)             += vf_pad_opencl.o opencl.o 
opencl/pad.o
diff --git a/libavfilter/vf_owdenoise.c b/libavfilter/vf_owdenoise.c
index 746c55a6e5d..55d6dd0f22f 100644
--- a/libavfilter/vf_owdenoise.c
+++ b/libavfilter/vf_owdenoise.c
@@ -41,6 +41,7 @@
 #include "avfilter.h"
 #include "filters.h"
 #include "video.h"
+#include "wavelet97.h"
 
 typedef struct OWDenoiseContext {
     const AVClass *class;
@@ -77,116 +78,6 @@ DECLARE_ALIGNED(8, static const uint8_t, dither)[8][8] = {
     { 42,  26,  38,  22,  41,  25,  37,  21 },
 };
 
-static const double coeff[2][5] = {
-    {
-         0.6029490182363579  * M_SQRT2,
-         0.2668641184428723  * M_SQRT2,
-        -0.07822326652898785 * M_SQRT2,
-        -0.01686411844287495 * M_SQRT2,
-         0.02674875741080976 * M_SQRT2,
-    },{
-         1.115087052456994   / M_SQRT2,
-        -0.5912717631142470  / M_SQRT2,
-        -0.05754352622849957 / M_SQRT2,
-         0.09127176311424948 / M_SQRT2,
-    }
-};
-
-static const double icoeff[2][5] = {
-    {
-         1.115087052456994   / M_SQRT2,
-         0.5912717631142470  / M_SQRT2,
-        -0.05754352622849957 / M_SQRT2,
-        -0.09127176311424948 / M_SQRT2,
-    },{
-         0.6029490182363579  * M_SQRT2,
-        -0.2668641184428723  * M_SQRT2,
-        -0.07822326652898785 * M_SQRT2,
-         0.01686411844287495 * M_SQRT2,
-         0.02674875741080976 * M_SQRT2,
-    }
-};
-
-
-static inline void decompose(float *dst_l, float *dst_h, const float *src,
-                             int linesize, int w)
-{
-    int x, i;
-    for (x = 0; x < w; x++) {
-        double sum_l = src[x * linesize] * coeff[0][0];
-        double sum_h = src[x * linesize] * coeff[1][0];
-        for (i = 1; i <= 4; i++) {
-            const double s = src[avpriv_mirror(x - i, w - 1) * linesize]
-                           + src[avpriv_mirror(x + i, w - 1) * linesize];
-
-            sum_l += coeff[0][i] * s;
-            sum_h += coeff[1][i] * s;
-        }
-        dst_l[x * linesize] = sum_l;
-        dst_h[x * linesize] = sum_h;
-    }
-}
-
-static inline void compose(float *dst, const float *src_l, const float *src_h,
-                           int linesize, int w)
-{
-    int x, i;
-    for (x = 0; x < w; x++) {
-        double sum_l = src_l[x * linesize] * icoeff[0][0];
-        double sum_h = src_h[x * linesize] * icoeff[1][0];
-        for (i = 1; i <= 4; i++) {
-            const int x0 = avpriv_mirror(x - i, w - 1) * linesize;
-            const int x1 = avpriv_mirror(x + i, w - 1) * linesize;
-
-            sum_l += icoeff[0][i] * (src_l[x0] + src_l[x1]);
-            sum_h += icoeff[1][i] * (src_h[x0] + src_h[x1]);
-        }
-        dst[x * linesize] = (sum_l + sum_h) * 0.5;
-    }
-}
-
-static inline void decompose2D(float *dst_l, float *dst_h, const float *src,
-                               int xlinesize, int ylinesize,
-                               int step, int w, int h)
-{
-    int y, x;
-    for (y = 0; y < h; y++)
-        for (x = 0; x < step; x++)
-            decompose(dst_l + ylinesize*y + xlinesize*x,
-                      dst_h + ylinesize*y + xlinesize*x,
-                      src   + ylinesize*y + xlinesize*x,
-                      step * xlinesize, (w - x + step - 1) / step);
-}
-
-static inline void compose2D(float *dst, const float *src_l, const float 
*src_h,
-                             int xlinesize, int ylinesize,
-                             int step, int w, int h)
-{
-    int y, x;
-    for (y = 0; y < h; y++)
-        for (x = 0; x < step; x++)
-            compose(dst   + ylinesize*y + xlinesize*x,
-                    src_l + ylinesize*y + xlinesize*x,
-                    src_h + ylinesize*y + xlinesize*x,
-                    step * xlinesize, (w - x + step - 1) / step);
-}
-
-static void decompose2D2(float *dst[4], float *src, float *temp[2],
-                         int linesize, int step, int w, int h)
-{
-    decompose2D(temp[0], temp[1], src,     1, linesize, step, w, h);
-    decompose2D( dst[0],  dst[1], temp[0], linesize, 1, step, h, w);
-    decompose2D( dst[2],  dst[3], temp[1], linesize, 1, step, h, w);
-}
-
-static void compose2D2(float *dst, float *src[4], float *temp[2],
-                       int linesize, int step, int w, int h)
-{
-    compose2D(temp[0],  src[0],  src[1], linesize, 1, step, h, w);
-    compose2D(temp[1],  src[2],  src[3], linesize, 1, step, h, w);
-    compose2D(dst,     temp[0], temp[1], 1, linesize, step, w, h);
-}
-
 static void filter(OWDenoiseContext *s,
                    uint8_t       *dst, int dst_linesize,
                    const uint8_t *src, int src_linesize,
@@ -211,7 +102,8 @@ static void filter(OWDenoiseContext *s,
     }
 
     for (i = 0; i < depth; i++)
-        decompose2D2(s->plane[i + 1], s->plane[i][0], s->plane[0] + 1, 
s->linesize, 1<<i, width, height);
+        ff_wavelet97_decompose(s->plane[i + 1], s->plane[i][0], s->plane[0] + 
1,
+                               ff_wavelet97_coeff, s->linesize, 1<<i, width, 
height);
 
     for (i = 0; i < depth; i++) {
         for (j = 1; j < 4; j++) {
@@ -227,7 +119,8 @@ static void filter(OWDenoiseContext *s,
         }
     }
     for (i = depth-1; i >= 0; i--)
-        compose2D2(s->plane[i][0], s->plane[i + 1], s->plane[0] + 1, 
s->linesize, 1<<i, width, height);
+        ff_wavelet97_compose(s->plane[i][0], s->plane[i + 1], s->plane[0] + 1,
+                             s->linesize, 1<<i, width, height);
 
     if (s->pixel_depth <= 8) {
         for (y = 0; y < height; y++) {
diff --git a/libavfilter/wavelet97.c b/libavfilter/wavelet97.c
new file mode 100644
index 00000000000..3a2a52e4178
--- /dev/null
+++ b/libavfilter/wavelet97.c
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2007 Michael Niedermayer <[email protected]>
+ * Copyright (c) 2013 Clément Bœsch <u pkh me>
+ *
+ * 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/internal.h"
+#include "libavutil/mathematics.h"
+#include "wavelet97.h"
+
+const double ff_wavelet97_coeff[2][5] = {
+    {
+         0.6029490182363579  * M_SQRT2,
+         0.2668641184428723  * M_SQRT2,
+        -0.07822326652898785 * M_SQRT2,
+        -0.01686411844287495 * M_SQRT2,
+         0.02674875741080976 * M_SQRT2,
+    },{
+         1.115087052456994   / M_SQRT2,
+        -0.5912717631142470  / M_SQRT2,
+        -0.05754352622849957 / M_SQRT2,
+         0.09127176311424948 / M_SQRT2,
+    }
+};
+
+static const double icoeff[2][5] = {
+    {
+         1.115087052456994   / M_SQRT2,
+         0.5912717631142470  / M_SQRT2,
+        -0.05754352622849957 / M_SQRT2,
+        -0.09127176311424948 / M_SQRT2,
+    },{
+         0.6029490182363579  * M_SQRT2,
+        -0.2668641184428723  * M_SQRT2,
+        -0.07822326652898785 * M_SQRT2,
+         0.01686411844287495 * M_SQRT2,
+         0.02674875741080976 * M_SQRT2,
+    }
+};
+
+static inline void decompose(float *dst_l, float *dst_h, const float *src,
+                             const double coeff[2][5], int linesize, int w)
+{
+    int x, i;
+    for (x = 0; x < w; x++) {
+        double sum_l = src[x * linesize] * coeff[0][0];
+        double sum_h = src[x * linesize] * coeff[1][0];
+        for (i = 1; i <= 4; i++) {
+            const double s = src[avpriv_mirror(x - i, w - 1) * linesize]
+                           + src[avpriv_mirror(x + i, w - 1) * linesize];
+
+            sum_l += coeff[0][i] * s;
+            sum_h += coeff[1][i] * s;
+        }
+        dst_l[x * linesize] = sum_l;
+        dst_h[x * linesize] = sum_h;
+    }
+}
+
+static inline void compose(float *dst, const float *src_l, const float *src_h,
+                           int linesize, int w)
+{
+    int x, i;
+    for (x = 0; x < w; x++) {
+        double sum_l = src_l[x * linesize] * icoeff[0][0];
+        double sum_h = src_h[x * linesize] * icoeff[1][0];
+        for (i = 1; i <= 4; i++) {
+            const int x0 = avpriv_mirror(x - i, w - 1) * linesize;
+            const int x1 = avpriv_mirror(x + i, w - 1) * linesize;
+
+            sum_l += icoeff[0][i] * (src_l[x0] + src_l[x1]);
+            sum_h += icoeff[1][i] * (src_h[x0] + src_h[x1]);
+        }
+        dst[x * linesize] = (sum_l + sum_h) * 0.5;
+    }
+}
+
+static inline void decompose2D(float *dst_l, float *dst_h, const float *src,
+                               const double coeff[2][5],
+                               int xlinesize, int ylinesize,
+                               int step, int w, int h)
+{
+    int y, x;
+    for (y = 0; y < h; y++)
+        for (x = 0; x < step; x++)
+            decompose(dst_l + ylinesize*y + xlinesize*x,
+                      dst_h + ylinesize*y + xlinesize*x,
+                      src   + ylinesize*y + xlinesize*x,
+                      coeff, step * xlinesize, (w - x + step - 1) / step);
+}
+
+static inline void compose2D(float *dst, const float *src_l, const float 
*src_h,
+                             int xlinesize, int ylinesize,
+                             int step, int w, int h)
+{
+    int y, x;
+    for (y = 0; y < h; y++)
+        for (x = 0; x < step; x++)
+            compose(dst   + ylinesize*y + xlinesize*x,
+                    src_l + ylinesize*y + xlinesize*x,
+                    src_h + ylinesize*y + xlinesize*x,
+                    step * xlinesize, (w - x + step - 1) / step);
+}
+
+void ff_wavelet97_decompose(float *dst[4], const float *src, float *temp[2],
+                            const double coeff[2][5],
+                            int linesize, int step, int w, int h)
+{
+    decompose2D(temp[0], temp[1], src,     coeff, 1, linesize, step, w, h);
+    decompose2D( dst[0],  dst[1], temp[0], coeff, linesize, 1, step, h, w);
+    decompose2D( dst[2],  dst[3], temp[1], coeff, linesize, 1, step, h, w);
+}
+
+void ff_wavelet97_compose(float *dst, float *const src[4], float *temp[2],
+                          int linesize, int step, int w, int h)
+{
+    compose2D(temp[0],  src[0],  src[1], linesize, 1, step, h, w);
+    compose2D(temp[1],  src[2],  src[3], linesize, 1, step, h, w);
+    compose2D(dst,     temp[0], temp[1], 1, linesize, step, w, h);
+}
diff --git a/libavfilter/wavelet97.h b/libavfilter/wavelet97.h
new file mode 100644
index 00000000000..956d841fa5a
--- /dev/null
+++ b/libavfilter/wavelet97.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2007 Michael Niedermayer <[email protected]>
+ * Copyright (c) 2013 Clément Bœsch <u pkh me>
+ *
+ * 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
+ */
+
+#ifndef AVFILTER_WAVELET97_H
+#define AVFILTER_WAVELET97_H
+
+/**
+ * Undecimated 9/7 wavelet transform
+ */
+
+/**
+ * Analysis lowpass and highpass filters, both symmetric around tap 0
+ */
+extern const double ff_wavelet97_coeff[2][5];
+
+/**
+ * One level of 2D analysis, filters are mirrored at the plane borders.
+ *
+ * @param dst      output bands LL, LH, HL, HH (horizontal/vertical filter)
+ * @param src      input plane
+ * @param temp     two scratch planes
+ * @param coeff    lowpass and highpass filters, normally ff_wavelet97_coeff
+ * @param linesize distance between lines in floats, same for all planes
+ * @param step     dilation of the filters, 1 << level
+ */
+void ff_wavelet97_decompose(float *dst[4], const float *src, float *temp[2],
+                            const double coeff[2][5],
+                            int linesize, int step, int w, int h);
+
+/**
+ * One level of 2D synthesis, the inverse of ff_wavelet97_decompose() with
+ * ff_wavelet97_coeff.
+ */
+void ff_wavelet97_compose(float *dst, float *const src[4], float *temp[2],
+                          int linesize, int step, int w, int h);
+
+#endif /* AVFILTER_WAVELET97_H */
-- 
2.54.0


From 8643e0a44ca8aa8bd7a3cace18f5357e5863f44a Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Tue, 27 Oct 2026 00:12:25 +0100
Subject: [PATCH 2/6] avfilter/vf_vignette: add a wavelet mode reversing the
 vignette with noise suppression

Dividing by the attenuation amplifies the noise and the quantization
steps of the dark areas by the same factor. The wavelet mode performs
the division in the pixel domain, then transforms the result and its
per pixel noise variance, propagated through the squared filter taps,
with the undecimated 9/7 wavelet. Each detail coefficient is Wiener
shrunk toward 0 with a signal variance estimated from a 7x7 box of
coefficients at the dilation of the level, weighted by their precision
and counted only when it exceeds the noise by 3 standard errors. The
lowpass of the top level averages over large areas and is kept, so the
shade of heavily attenuated areas survives.

Assisted-by: Claude
---
 doc/filters.texi          |  11 +++
 libavfilter/Makefile      |   2 +-
 libavfilter/vf_vignette.c | 173 ++++++++++++++++++++++++++++++++++++--
 3 files changed, 179 insertions(+), 7 deletions(-)

diff --git a/doc/filters.texi b/doc/filters.texi
index 37f9123df49..a6cdc96ad04 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -26120,10 +26120,21 @@ The larger the distance from the central point, the 
brighter the image becomes.
 This can be used to reverse a vignette effect, though there is no automatic
 detection to extract the lens @option{angle} and other settings (yet). It can
 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.
 @end table
 
 Default value is @samp{forward}.
 
+@item noise
+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 eval
 Set evaluation mode for the expressions (@option{angle}, @option{x0}, 
@option{y0}).
 
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 8669ff192a0..b2ecd848905 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -573,7 +573,7 @@ OBJS-$(CONFIG_VIBRANCE_FILTER)               += 
vf_vibrance.o
 OBJS-$(CONFIG_VIDSTABDETECT_FILTER)          += vidstabutils.o 
vf_vidstabdetect.o
 OBJS-$(CONFIG_VIDSTABTRANSFORM_FILTER)       += vidstabutils.o 
vf_vidstabtransform.o
 OBJS-$(CONFIG_VIF_FILTER)                    += vf_vif.o framesync.o
-OBJS-$(CONFIG_VIGNETTE_FILTER)               += vf_vignette.o
+OBJS-$(CONFIG_VIGNETTE_FILTER)               += vf_vignette.o wavelet97.o
 OBJS-$(CONFIG_VMAFMOTION_FILTER)             += vf_vmafmotion.o framesync.o
 OBJS-$(CONFIG_VPP_AMF_FILTER)                += vf_vpp_amf.o scale_eval.o 
vf_amf_common.o
 OBJS-$(CONFIG_FRC_AMF_FILTER)                += vf_frc_amf.o vf_amf_common.o
diff --git a/libavfilter/vf_vignette.c b/libavfilter/vf_vignette.c
index da4c3079648..2a88a9e5f84 100644
--- a/libavfilter/vf_vignette.c
+++ b/libavfilter/vf_vignette.c
@@ -23,10 +23,12 @@
 #include "libavutil/mem.h"
 #include "libavutil/opt.h"
 #include "libavutil/eval.h"
+#include "libavutil/internal.h"
 #include "libavutil/pixdesc.h"
 #include "avfilter.h"
 #include "filters.h"
 #include "video.h"
+#include "wavelet97.h"
 
 static const char *const var_names[] = {
     "w",    // stream width
@@ -56,11 +58,21 @@ enum EvalMode {
     EVAL_MODE_NB
 };
 
+enum Mode {
+    MODE_FORWARD,
+    MODE_BACKWARD,
+    MODE_WAVELET,
+};
+
 typedef struct VignetteContext {
     const AVClass *class;
     const AVPixFmtDescriptor *desc;
-    int backward;
+    int mode;                           ///< Mode
     int eval_mode;                      ///< EvalMode
+    double noise;
+    double sqcoeff[2][5];
+    float *wplanes;
+    int wplane_size;
 #define DEF_EXPR_FIELDS(name) AVExpr *name##_pexpr; char *name##_expr; double 
name
     DEF_EXPR_FIELDS(angle);
     DEF_EXPR_FIELDS(x0);
@@ -83,9 +95,11 @@ static const AVOption vignette_options[] = {
     { "a",     "set lens angle", OFFSET(angle_expr), AV_OPT_TYPE_STRING, 
{.str="PI/5"}, .flags = FLAGS },
     { "x0", "set circle center position on x-axis", OFFSET(x0_expr), 
AV_OPT_TYPE_STRING, {.str="w/2"}, .flags = FLAGS },
     { "y0", "set circle center position on y-axis", OFFSET(y0_expr), 
AV_OPT_TYPE_STRING, {.str="h/2"}, .flags = FLAGS },
-    { "mode", "set forward/backward mode", OFFSET(backward), AV_OPT_TYPE_INT, 
{.i64 = 0}, 0, 1, FLAGS, .unit = "mode" },
-        { "forward",  NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 0}, INT_MIN, 
INT_MAX, FLAGS, .unit = "mode"},
-        { "backward", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 1}, INT_MIN, 
INT_MAX, FLAGS, .unit = "mode"},
+    { "mode", "set forward/backward mode", OFFSET(mode), AV_OPT_TYPE_INT, 
{.i64 = MODE_FORWARD}, 0, MODE_WAVELET, FLAGS, .unit = "mode" },
+        { "forward",  NULL, 0, AV_OPT_TYPE_CONST, {.i64 = MODE_FORWARD},  
INT_MIN, INT_MAX, FLAGS, .unit = "mode"},
+        { "backward", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = MODE_BACKWARD}, 
INT_MIN, INT_MAX, FLAGS, .unit = "mode"},
+        { "wavelet",  "backward with noise suppression in the wavelet domain", 
0, AV_OPT_TYPE_CONST, {.i64 = MODE_WAVELET}, INT_MIN, INT_MAX, FLAGS, .unit = 
"mode"},
+    { "noise", "set input noise standard deviation for wavelet mode", 
OFFSET(noise), AV_OPT_TYPE_DOUBLE, {.dbl = 0.5}, 0.01, 255, FLAGS },
     { "eval", "specify when to evaluate expressions", OFFSET(eval_mode), 
AV_OPT_TYPE_INT, {.i64 = EVAL_MODE_INIT}, 0, EVAL_MODE_NB-1, FLAGS, .unit = 
"eval" },
         { "init",  "eval expressions once during initialization", 0, 
AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_INIT},  .flags = FLAGS, .unit = "eval" },
         { "frame", "eval expressions for each frame",             0, 
AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_FRAME}, .flags = FLAGS, .unit = "eval" },
@@ -113,6 +127,10 @@ static av_cold int init(AVFilterContext *ctx)
     PARSE_EXPR(angle);
     PARSE_EXPR(x0);
     PARSE_EXPR(y0);
+
+    for (int i = 0; i < 2; i++)
+        for (int j = 0; j < 5; j++)
+            s->sqcoeff[i][j] = ff_wavelet97_coeff[i][j] * 
ff_wavelet97_coeff[i][j];
     return 0;
 }
 
@@ -120,6 +138,7 @@ static av_cold void uninit(AVFilterContext *ctx)
 {
     VignetteContext *s = ctx->priv;
     av_freep(&s->fmap);
+    av_freep(&s->wplanes);
     av_expr_free(s->angle_pexpr);
     av_expr_free(s->x0_pexpr);
     av_expr_free(s->y0_pexpr);
@@ -173,7 +192,7 @@ static void update_context(VignetteContext *s, AVFilterLink 
*inlink, AVFrame *fr
 
     s->angle = av_clipf(s->angle, 0, M_PI_2);
 
-    if (s->backward) {
+    if (s->mode == MODE_BACKWARD) {
         for (y = 0; y < inlink->h; y++) {
             for (x = 0; x < inlink->w; x++)
                 dst[x] = 1. / get_natural_factor(s, x, y);
@@ -198,6 +217,139 @@ static inline double get_dither_value(VignetteContext *s)
     return dv;
 }
 
+static int wavelet_levels(int w, int h)
+{
+    return FFMAX(av_log2(FFMIN(w, h)) - 2, 0);
+}
+
+#define BOX_RADIUS 3
+
+static void box_filter(float *dst, const float *src, float *temp,
+                       int linesize, int step, int w, int h)
+{
+    for (int y = 0; y < h; y++) {
+        for (int x = 0; x < w; x++) {
+            double sum = 0;
+
+            for (int k = -BOX_RADIUS; k <= BOX_RADIUS; k++)
+                sum += src[y * linesize + avpriv_mirror(x + k * step, w - 1)];
+            temp[y * linesize + x] = sum / (2 * BOX_RADIUS + 1);
+        }
+    }
+    for (int y = 0; y < h; y++) {
+        for (int x = 0; x < w; x++) {
+            double sum = 0;
+
+            for (int k = -BOX_RADIUS; k <= BOX_RADIUS; k++)
+                sum += temp[avpriv_mirror(y + k * step, h - 1) * linesize + x];
+            dst[y * linesize + x] = sum / (2 * BOX_RADIUS + 1);
+        }
+    }
+}
+
+static void shrink_band(float *band, const float *var, float *temp[3],
+                        int linesize, int step, int w, int h)
+{
+    // energy a box of coefficients must have to count as signal: noise + 3 
standard errors
+    const double threshold = 1 + 3 * sqrt(2.0 / ((2 * BOX_RADIUS + 1) * (2 * 
BOX_RADIUS + 1)));
+
+    for (int y = 0; y < h; y++) {
+        for (int x = 0; x < w; x++) {
+            const int i = y * linesize + x;
+
+            temp[0][i] = band[i] * band[i] / var[i];
+            temp[1][i] = 1 / var[i];
+        }
+    }
+    box_filter(temp[0], temp[0], temp[2], linesize, step, w, h);
+    box_filter(temp[1], temp[1], temp[2], linesize, step, w, h);
+
+    for (int y = 0; y < h; y++) {
+        for (int x = 0; x < w; x++) {
+            const int i = y * linesize + x;
+            const float signal = FFMAX(temp[0][i] - threshold, 0) / temp[1][i];
+
+            band[i] *= signal / (signal + var[i]);
+        }
+    }
+}
+
+static void filter_plane_wavelet(VignetteContext *s,
+                                 uint8_t *dst, int dst_linesize,
+                                 const uint8_t *src, int src_linesize, int 
pixel_step,
+                                 int w, int h, int hsub, int vsub, int bias)
+{
+    const int linesize   = s->fmap_linesize;
+    const int plane_size = s->wplane_size;
+    const int levels     = wavelet_levels(w, h);
+    float *cur       = s->wplanes;
+    float *next      = cur + plane_size;
+    float *var       = next + plane_size;
+    float *temp[2]   = { var + plane_size, var + 2 * plane_size };
+    float *vbands[4] = { var + 3 * plane_size, var + 4 * plane_size,
+                         var + 5 * plane_size, var + 6 * plane_size };
+    float *hbands    = var + 7 * plane_size;
+
+    for (int y = 0; y < h; y++) {
+        for (int x = 0; x < w; x++) {
+            const double a = FFMAX(s->fmap[(y << vsub) * linesize + (x << 
hsub)], 1e-6);
+
+            cur[y * linesize + x] = av_clipd((src[y * src_linesize + x * 
pixel_step] - bias) / a,
+                                             -bias, 255 - bias);
+            var[y * linesize + x] = s->noise * s->noise / (a * a);
+        }
+    }
+
+    for (int i = 0; i < levels; i++) {
+        float *bands[4] = { next, hbands + 3 * i * plane_size,
+                            hbands + (3 * i + 1) * plane_size, hbands + (3 * i 
+ 2) * plane_size };
+
+        float *scratch[3] = { temp[0], temp[1], cur };
+
+        ff_wavelet97_decompose(bands,  cur, temp, ff_wavelet97_coeff, 
linesize, 1 << i, w, h);
+        ff_wavelet97_decompose(vbands, var, temp, s->sqcoeff,         
linesize, 1 << i, w, h);
+        for (int j = 1; j < 4; j++)
+            shrink_band(bands[j], vbands[j], scratch, linesize, 1 << i, w, h);
+        FFSWAP(float *, cur, next);
+        FFSWAP(float *, var, vbands[0]);
+    }
+
+    for (int i = levels - 1; i >= 0; i--) {
+        float *bands[4] = { cur, hbands + 3 * i * plane_size,
+                            hbands + (3 * i + 1) * plane_size, hbands + (3 * i 
+ 2) * plane_size };
+
+        ff_wavelet97_compose(next, bands, temp, linesize, 1 << i, w, h);
+        FFSWAP(float *, cur, next);
+    }
+
+    for (int y = 0; y < h; y++)
+        for (int x = 0; x < w; x++)
+            dst[y * dst_linesize + x * pixel_step] =
+                av_clip_uint8(cur[y * linesize + x] + bias + 
get_dither_value(s));
+}
+
+static void filter_frame_wavelet(VignetteContext *s, AVFrame *out, const 
AVFrame *in)
+{
+    if (s->desc->flags & AV_PIX_FMT_FLAG_RGB) {
+        for (int c = 0; c < 3; c++)
+            filter_plane_wavelet(s, out->data[0] + c, out->linesize[0],
+                                 in->data[0] + c, in->linesize[0], 3,
+                                 in->width, in->height, 0, 0, 0);
+        return;
+    }
+
+    for (int plane = 0; plane < 4 && in->data[plane] && in->linesize[plane]; 
plane++) {
+        const int chroma = plane == 1 || plane == 2;
+        const int hsub = chroma ? s->desc->log2_chroma_w : 0;
+        const int vsub = chroma ? s->desc->log2_chroma_h : 0;
+
+        filter_plane_wavelet(s, out->data[plane], out->linesize[plane],
+                             in->data[plane], in->linesize[plane], 1,
+                             AV_CEIL_RSHIFT(in->width, hsub), 
AV_CEIL_RSHIFT(in->height, vsub),
+                             hsub, vsub, chroma ? 127 : 0);
+    }
+}
+
 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
 {
     unsigned x, y, direct = 0;
@@ -221,7 +373,9 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
     if (s->eval_mode == EVAL_MODE_FRAME)
         update_context(s, inlink, in);
 
-    if (s->desc->flags & AV_PIX_FMT_FLAG_RGB) {
+    if (s->mode == MODE_WAVELET) {
+        filter_frame_wavelet(s, out, in);
+    } else if (s->desc->flags & AV_PIX_FMT_FLAG_RGB) {
         uint8_t       *dst = out->data[0];
         const uint8_t *src = in ->data[0];
         const float *fmap = s->fmap;
@@ -313,6 +467,13 @@ static int config_props(AVFilterLink *inlink)
     if (!s->fmap)
         return AVERROR(ENOMEM);
 
+    if (s->mode == MODE_WAVELET) {
+        s->wplane_size = s->fmap_linesize * inlink->h;
+        s->wplanes = av_malloc_array(s->wplane_size, (9 + 3 * 
wavelet_levels(inlink->w, inlink->h)) * sizeof(*s->wplanes));
+        if (!s->wplanes)
+            return AVERROR(ENOMEM);
+    }
+
     if (s->eval_mode == EVAL_MODE_INIT)
         update_context(s, inlink, NULL);
 
-- 
2.54.0


From 8522ba9ccaa657896bd26b777853d9c61c459879 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Tue, 27 Oct 2026 01:37:23 +0100
Subject: [PATCH 3/6] avfilter/wavelet97: expose the per line analysis and
 synthesis

Lets a filter run the row and column passes over ranges of lines, as
slice threading needs.

Assisted-by: Claude
---
 libavfilter/wavelet97.c | 30 +++++++++++++++---------------
 libavfilter/wavelet97.h | 23 ++++++++++++++++++++++-
 2 files changed, 37 insertions(+), 16 deletions(-)

diff --git a/libavfilter/wavelet97.c b/libavfilter/wavelet97.c
index 3a2a52e4178..c639b8d596e 100644
--- a/libavfilter/wavelet97.c
+++ b/libavfilter/wavelet97.c
@@ -90,13 +90,13 @@ static inline void compose(float *dst, const float *src_l, 
const float *src_h,
     }
 }
 
-static inline void decompose2D(float *dst_l, float *dst_h, const float *src,
-                               const double coeff[2][5],
-                               int xlinesize, int ylinesize,
-                               int step, int w, int h)
+void ff_wavelet97_decompose_lines(float *dst_l, float *dst_h, const float *src,
+                                  const double coeff[2][5],
+                                  int xlinesize, int ylinesize,
+                                  int step, int w, int line0, int line1)
 {
     int y, x;
-    for (y = 0; y < h; y++)
+    for (y = line0; y < line1; y++)
         for (x = 0; x < step; x++)
             decompose(dst_l + ylinesize*y + xlinesize*x,
                       dst_h + ylinesize*y + xlinesize*x,
@@ -104,12 +104,12 @@ static inline void decompose2D(float *dst_l, float 
*dst_h, const float *src,
                       coeff, step * xlinesize, (w - x + step - 1) / step);
 }
 
-static inline void compose2D(float *dst, const float *src_l, const float 
*src_h,
-                             int xlinesize, int ylinesize,
-                             int step, int w, int h)
+void ff_wavelet97_compose_lines(float *dst, const float *src_l, const float 
*src_h,
+                                int xlinesize, int ylinesize,
+                                int step, int w, int line0, int line1)
 {
     int y, x;
-    for (y = 0; y < h; y++)
+    for (y = line0; y < line1; y++)
         for (x = 0; x < step; x++)
             compose(dst   + ylinesize*y + xlinesize*x,
                     src_l + ylinesize*y + xlinesize*x,
@@ -121,15 +121,15 @@ void ff_wavelet97_decompose(float *dst[4], const float 
*src, float *temp[2],
                             const double coeff[2][5],
                             int linesize, int step, int w, int h)
 {
-    decompose2D(temp[0], temp[1], src,     coeff, 1, linesize, step, w, h);
-    decompose2D( dst[0],  dst[1], temp[0], coeff, linesize, 1, step, h, w);
-    decompose2D( dst[2],  dst[3], temp[1], coeff, linesize, 1, step, h, w);
+    ff_wavelet97_decompose_lines(temp[0], temp[1], src,     coeff, 1, 
linesize, step, w, 0, h);
+    ff_wavelet97_decompose_lines( dst[0],  dst[1], temp[0], coeff, linesize, 
1, step, h, 0, w);
+    ff_wavelet97_decompose_lines( dst[2],  dst[3], temp[1], coeff, linesize, 
1, step, h, 0, w);
 }
 
 void ff_wavelet97_compose(float *dst, float *const src[4], float *temp[2],
                           int linesize, int step, int w, int h)
 {
-    compose2D(temp[0],  src[0],  src[1], linesize, 1, step, h, w);
-    compose2D(temp[1],  src[2],  src[3], linesize, 1, step, h, w);
-    compose2D(dst,     temp[0], temp[1], 1, linesize, step, w, h);
+    ff_wavelet97_compose_lines(temp[0],  src[0],  src[1], linesize, 1, step, 
h, 0, w);
+    ff_wavelet97_compose_lines(temp[1],  src[2],  src[3], linesize, 1, step, 
h, 0, w);
+    ff_wavelet97_compose_lines(dst,     temp[0], temp[1], 1, linesize, step, 
w, 0, h);
 }
diff --git a/libavfilter/wavelet97.h b/libavfilter/wavelet97.h
index 956d841fa5a..16e3ff61908 100644
--- a/libavfilter/wavelet97.h
+++ b/libavfilter/wavelet97.h
@@ -32,7 +32,28 @@
 extern const double ff_wavelet97_coeff[2][5];
 
 /**
- * One level of 2D analysis, filters are mirrored at the plane borders.
+ * Undecimated analysis of lines line0..line1-1 along one direction.
+ * Line y, sample x is at src[y * ylinesize + x * xlinesize], x < w.
+ *
+ * @param dst_l    lowpass output, same layout as src
+ * @param dst_h    highpass output, same layout as src
+ * @param coeff    lowpass and highpass filters, normally ff_wavelet97_coeff
+ * @param step     dilation of the filters, 1 << level
+ */
+void ff_wavelet97_decompose_lines(float *dst_l, float *dst_h, const float *src,
+                                  const double coeff[2][5],
+                                  int xlinesize, int ylinesize,
+                                  int step, int w, int line0, int line1);
+
+/**
+ * Inverse of ff_wavelet97_decompose_lines() with ff_wavelet97_coeff.
+ */
+void ff_wavelet97_compose_lines(float *dst, const float *src_l, const float 
*src_h,
+                                int xlinesize, int ylinesize,
+                                int step, int w, int line0, int line1);
+
+/**
+ * One level of undecimated 2D analysis, filters are mirrored at the plane 
borders.
  *
  * @param dst      output bands LL, LH, HL, HH (horizontal/vertical filter)
  * @param src      input plane
-- 
2.54.0


From 616ee0e78c0086f7dcf6bc7c56bfcb87767f01c0 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Tue, 27 Oct 2026 01:37:34 +0100
Subject: [PATCH 4/6] avfilter/wavelet97: add a decimated transform

Lowpass coefficients are stored at even and highpass ones at odd
positions of the line, so a level works in place on the lattice of the
previous level's lowpass coefficients. Reconstruction from a single
polyphase set needs no averaging, hence no factor 0.5.

Assisted-by: Claude
---
 libavfilter/wavelet97.c | 44 +++++++++++++++++++++++++++++++++++++++++
 libavfilter/wavelet97.h | 19 ++++++++++++++++++
 2 files changed, 63 insertions(+)

diff --git a/libavfilter/wavelet97.c b/libavfilter/wavelet97.c
index c639b8d596e..65a9b8ebc0e 100644
--- a/libavfilter/wavelet97.c
+++ b/libavfilter/wavelet97.c
@@ -90,6 +90,33 @@ static inline void compose(float *dst, const float *src_l, 
const float *src_h,
     }
 }
 
+static inline void decompose_decimated(float *dst, const float *src,
+                                       const double coeff[2][5], int stride, 
int n)
+{
+    int x, i;
+    for (x = 0; x < n; x++) {
+        const double *c = coeff[x & 1];
+        double sum = src[x * stride] * c[0];
+        for (i = 1; i <= 4; i++)
+            sum += c[i] * (src[avpriv_mirror(x - i, n - 1) * stride]
+                         + src[avpriv_mirror(x + i, n - 1) * stride]);
+        dst[x * stride] = sum;
+    }
+}
+
+static inline void compose_decimated(float *dst, const float *src,
+                                     int stride, int n)
+{
+    int x, i;
+    for (x = 0; x < n; x++) {
+        double sum = src[x * stride] * icoeff[x & 1][0];
+        for (i = 1; i <= 4; i++)
+            sum += icoeff[(x + i) & 1][i] * (src[avpriv_mirror(x - i, n - 1) * 
stride]
+                                           + src[avpriv_mirror(x + i, n - 1) * 
stride]);
+        dst[x * stride] = sum;
+    }
+}
+
 void ff_wavelet97_decompose_lines(float *dst_l, float *dst_h, const float *src,
                                   const double coeff[2][5],
                                   int xlinesize, int ylinesize,
@@ -117,6 +144,23 @@ void ff_wavelet97_compose_lines(float *dst, const float 
*src_l, const float *src
                     step * xlinesize, (w - x + step - 1) / step);
 }
 
+void ff_wavelet97_decompose_decimated_lines(float *dst, const float *src,
+                                            const double coeff[2][5],
+                                            int xstride, int ystride,
+                                            int len, int line0, int line1)
+{
+    for (int y = line0; y < line1; y++)
+        decompose_decimated(dst + ystride*y, src + ystride*y, coeff, xstride, 
len);
+}
+
+void ff_wavelet97_compose_decimated_lines(float *dst, const float *src,
+                                          int xstride, int ystride,
+                                          int len, int line0, int line1)
+{
+    for (int y = line0; y < line1; y++)
+        compose_decimated(dst + ystride*y, src + ystride*y, xstride, len);
+}
+
 void ff_wavelet97_decompose(float *dst[4], const float *src, float *temp[2],
                             const double coeff[2][5],
                             int linesize, int step, int w, int h)
diff --git a/libavfilter/wavelet97.h b/libavfilter/wavelet97.h
index 16e3ff61908..018139f1c60 100644
--- a/libavfilter/wavelet97.h
+++ b/libavfilter/wavelet97.h
@@ -52,6 +52,25 @@ void ff_wavelet97_compose_lines(float *dst, const float 
*src_l, const float *src
                                 int xlinesize, int ylinesize,
                                 int step, int w, int line0, int line1);
 
+/**
+ * Decimated analysis of lines line0..line1-1 along one direction.
+ * Line y, sample x is at src[y * ystride + x * xstride], x < len.
+ * Lowpass coefficients are written to even, highpass ones to odd x of dst.
+ *
+ * @param coeff    lowpass and highpass filters, normally ff_wavelet97_coeff
+ */
+void ff_wavelet97_decompose_decimated_lines(float *dst, const float *src,
+                                            const double coeff[2][5],
+                                            int xstride, int ystride,
+                                            int len, int line0, int line1);
+
+/**
+ * Inverse of ff_wavelet97_decompose_decimated_lines() with ff_wavelet97_coeff.
+ */
+void ff_wavelet97_compose_decimated_lines(float *dst, const float *src,
+                                          int xstride, int ystride,
+                                          int len, int line0, int line1);
+
 /**
  * One level of undecimated 2D analysis, filters are mirrored at the plane 
borders.
  *
-- 
2.54.0


From 8585d2708c364c259cd37a047239966f1d881960 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Tue, 27 Oct 2026 01:38:06 +0100
Subject: [PATCH 5/6] avfilter/vf_vignette: slice threading for the wavelet
 mode

Every pass over a plane, the row and column passes of the transforms as
well as the box filters and the per coefficient operations of the
shrinkage, is split into slices of lines. The output does not depend on
the number of threads.

Assisted-by: Claude
---
 libavfilter/vf_vignette.c | 261 ++++++++++++++++++++++++++++----------
 1 file changed, 191 insertions(+), 70 deletions(-)

diff --git a/libavfilter/vf_vignette.c b/libavfilter/vf_vignette.c
index 2a88a9e5f84..4aec1a909c4 100644
--- a/libavfilter/vf_vignette.c
+++ b/libavfilter/vf_vignette.c
@@ -224,71 +224,210 @@ static int wavelet_levels(int w, int h)
 
 #define BOX_RADIUS 3
 
-static void box_filter(float *dst, const float *src, float *temp,
-                       int linesize, int step, int w, int h)
+enum PassType {
+    PASS_DECOMPOSE,
+    PASS_COMPOSE,
+    PASS_BOX,
+    PASS_ENERGY,
+    PASS_GAIN,
+};
+
+/**
+ * Samples of a plane processed by a pass, sample x of line y is at
+ * y * ystride + x * xstride, x < w, y < h.
+ */
+typedef struct Lattice {
+    int xstride, ystride, w, h;
+} Lattice;
+
+static Lattice transpose(Lattice l)
 {
-    for (int y = 0; y < h; y++) {
-        for (int x = 0; x < w; x++) {
+    return (Lattice){ l.ystride, l.xstride, l.h, l.w };
+}
+
+/**
+ * One operation on the lines of a lattice, run in parallel over slices of 
lines
+ */
+typedef struct WaveletPass {
+    enum PassType type;
+    Lattice lat;
+    float *dst[2];
+    const float *src[3];
+    const double (*coeff)[5];
+    int step;                           ///< filter dilation
+} WaveletPass;
+
+static void box_lines(const WaveletPass *p, int line0, int line1)
+{
+    const Lattice l = p->lat;
+
+    for (int y = line0; y < line1; y++) {
+        for (int x = 0; x < l.w; x++) {
             double sum = 0;
 
             for (int k = -BOX_RADIUS; k <= BOX_RADIUS; k++)
-                sum += src[y * linesize + avpriv_mirror(x + k * step, w - 1)];
-            temp[y * linesize + x] = sum / (2 * BOX_RADIUS + 1);
-        }
-    }
-    for (int y = 0; y < h; y++) {
-        for (int x = 0; x < w; x++) {
-            double sum = 0;
-
-            for (int k = -BOX_RADIUS; k <= BOX_RADIUS; k++)
-                sum += temp[avpriv_mirror(y + k * step, h - 1) * linesize + x];
-            dst[y * linesize + x] = sum / (2 * BOX_RADIUS + 1);
+                sum += p->src[0][y * l.ystride + avpriv_mirror(x + k * 
p->step, l.w - 1) * l.xstride];
+            p->dst[0][y * l.ystride + x * l.xstride] = sum / (2 * BOX_RADIUS + 
1);
         }
     }
 }
 
-static void shrink_band(float *band, const float *var, float *temp[3],
-                        int linesize, int step, int w, int h)
+static void energy_lines(const WaveletPass *p, int line0, int line1)
 {
+    const Lattice l = p->lat;
+
+    for (int y = line0; y < line1; y++) {
+        for (int x = 0; x < l.w; x++) {
+            const int i = y * l.ystride + x * l.xstride;
+            const float band = p->src[0][i], var = p->src[1][i];
+
+            p->dst[0][i] = band * band / var;
+            p->dst[1][i] = 1 / var;
+        }
+    }
+}
+
+static void gain_lines(const WaveletPass *p, int line0, int line1)
+{
+    const Lattice l = p->lat;
     // energy a box of coefficients must have to count as signal: noise + 3 
standard errors
     const double threshold = 1 + 3 * sqrt(2.0 / ((2 * BOX_RADIUS + 1) * (2 * 
BOX_RADIUS + 1)));
 
-    for (int y = 0; y < h; y++) {
-        for (int x = 0; x < w; x++) {
-            const int i = y * linesize + x;
+    for (int y = line0; y < line1; y++) {
+        for (int x = 0; x < l.w; x++) {
+            const int i = y * l.ystride + x * l.xstride;
+            const float signal = FFMAX(p->src[0][i] - threshold, 0) / 
p->src[1][i];
 
-            temp[0][i] = band[i] * band[i] / var[i];
-            temp[1][i] = 1 / var[i];
-        }
-    }
-    box_filter(temp[0], temp[0], temp[2], linesize, step, w, h);
-    box_filter(temp[1], temp[1], temp[2], linesize, step, w, h);
-
-    for (int y = 0; y < h; y++) {
-        for (int x = 0; x < w; x++) {
-            const int i = y * linesize + x;
-            const float signal = FFMAX(temp[0][i] - threshold, 0) / temp[1][i];
-
-            band[i] *= signal / (signal + var[i]);
+            p->dst[0][i] *= signal / (signal + p->src[2][i]);
         }
     }
 }
 
-static void filter_plane_wavelet(VignetteContext *s,
+static int run_pass_slice(AVFilterContext *ctx, void *arg, int jobnr, int 
nb_jobs)
+{
+    const WaveletPass *p = arg;
+    const Lattice l = p->lat;
+    const int line0 = l.h *  jobnr      / nb_jobs;
+    const int line1 = l.h * (jobnr + 1) / nb_jobs;
+
+    switch (p->type) {
+    case PASS_DECOMPOSE:
+        ff_wavelet97_decompose_lines(p->dst[0], p->dst[1], p->src[0], p->coeff,
+                                     l.xstride, l.ystride, p->step, l.w, 
line0, line1);
+        break;
+    case PASS_COMPOSE:
+        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_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;
+    }
+    return 0;
+}
+
+static void run_pass(AVFilterContext *ctx, WaveletPass *p)
+{
+    if (p->lat.h > 0)
+        ff_filter_execute(ctx, run_pass_slice, p, NULL,
+                          FFMIN(p->lat.h, ff_filter_get_nb_threads(ctx)));
+}
+
+static void decompose(AVFilterContext *ctx, float *dst[4], const float *src, 
float *temp[2],
+                      const double coeff[2][5], int step, Lattice lat)
+{
+    WaveletPass p = { PASS_DECOMPOSE, lat, { temp[0], temp[1] }, { src }, 
coeff, step };
+
+    run_pass(ctx, &p);
+    p.lat = transpose(lat);
+    p.src[0] = temp[0]; p.dst[0] = dst[0]; p.dst[1] = dst[1];
+    run_pass(ctx, &p);
+    p.src[0] = temp[1]; p.dst[0] = dst[2]; p.dst[1] = dst[3];
+    run_pass(ctx, &p);
+}
+
+static void compose(AVFilterContext *ctx, float *dst, float *const src[4], 
float *temp[2],
+                    int step, Lattice lat)
+{
+    WaveletPass p = { PASS_COMPOSE, transpose(lat), { temp[0] }, { src[0], 
src[1] }, NULL, step };
+
+    run_pass(ctx, &p);
+    p.dst[0] = temp[1]; p.src[0] = src[2]; p.src[1] = src[3];
+    run_pass(ctx, &p);
+    p.lat = lat;
+    p.dst[0] = dst; p.src[0] = temp[0]; p.src[1] = temp[1];
+    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 };
+
+    run_pass(ctx, &p);
+    p.lat = transpose(lat);
+    p.dst[0] = plane; p.src[0] = temp;
+    run_pass(ctx, &p);
+}
+
+/**
+ * Wiener shrinkage of a detail band toward 0, the signal variance is estimated
+ * from a box of coefficients, weighted by their precision.
+ */
+static void shrink_band(AVFilterContext *ctx, float *band, const float *var, 
float *temp[3],
+                        int step, Lattice lat)
+{
+    WaveletPass p = { PASS_ENERGY, lat, { temp[0], temp[1] }, { band, var } };
+
+    run_pass(ctx, &p);
+    box_filter(ctx, temp[0], temp[2], step, lat);
+    box_filter(ctx, temp[1], temp[2], step, lat);
+    p.type = PASS_GAIN;
+    p.dst[0] = band; p.src[0] = temp[0]; p.src[1] = temp[1]; p.src[2] = var;
+    run_pass(ctx, &p);
+}
+
+static void transform(AVFilterContext *ctx, float *cur, float *var, int 
levels, Lattice lat)
+{
+    VignetteContext *s = ctx->priv;
+    const int plane_size = s->wplane_size;
+    float *next      = var + plane_size;
+    float *temp[2]   = { var + 2 * plane_size, var + 3 * plane_size };
+    float *vbands[4] = { var + 4 * plane_size, var + 5 * plane_size,
+                         var + 6 * plane_size, var + 7 * plane_size };
+    float *hbands    = var + 8 * plane_size;
+
+    for (int i = 0; i < levels; i++) {
+        float *bands[4] = { next, hbands + 3 * i * plane_size,
+                            hbands + (3 * i + 1) * plane_size, hbands + (3 * i 
+ 2) * plane_size };
+        float *scratch[3] = { temp[0], temp[1], cur };
+
+        decompose(ctx, bands,  cur, temp, ff_wavelet97_coeff, 1 << i, lat);
+        decompose(ctx, vbands, var, temp, s->sqcoeff,         1 << i, lat);
+        for (int j = 1; j < 4; j++)
+            shrink_band(ctx, bands[j], vbands[j], scratch, 1 << i, lat);
+        FFSWAP(float *, cur, next);
+        FFSWAP(float *, var, vbands[0]);
+    }
+
+    for (int i = levels - 1; i >= 0; i--) {
+        float *bands[4] = { cur, hbands + 3 * i * plane_size,
+                            hbands + (3 * i + 1) * plane_size, hbands + (3 * i 
+ 2) * plane_size };
+
+        compose(ctx, next, bands, temp, 1 << i, lat);
+        FFSWAP(float *, cur, next);
+    }
+}
+
+static void filter_plane_wavelet(AVFilterContext *ctx,
                                  uint8_t *dst, int dst_linesize,
                                  const uint8_t *src, int src_linesize, int 
pixel_step,
                                  int w, int h, int hsub, int vsub, int bias)
 {
-    const int linesize   = s->fmap_linesize;
-    const int plane_size = s->wplane_size;
-    const int levels     = wavelet_levels(w, h);
-    float *cur       = s->wplanes;
-    float *next      = cur + plane_size;
-    float *var       = next + plane_size;
-    float *temp[2]   = { var + plane_size, var + 2 * plane_size };
-    float *vbands[4] = { var + 3 * plane_size, var + 4 * plane_size,
-                         var + 5 * plane_size, var + 6 * plane_size };
-    float *hbands    = var + 7 * plane_size;
+    VignetteContext *s = ctx->priv;
+    const int linesize = s->fmap_linesize;
+    const Lattice lat  = { 1, linesize, w, h };
+    float *cur = s->wplanes;
+    float *var = cur + s->wplane_size;
 
     for (int y = 0; y < h; y++) {
         for (int x = 0; x < w; x++) {
@@ -300,27 +439,7 @@ static void filter_plane_wavelet(VignetteContext *s,
         }
     }
 
-    for (int i = 0; i < levels; i++) {
-        float *bands[4] = { next, hbands + 3 * i * plane_size,
-                            hbands + (3 * i + 1) * plane_size, hbands + (3 * i 
+ 2) * plane_size };
-
-        float *scratch[3] = { temp[0], temp[1], cur };
-
-        ff_wavelet97_decompose(bands,  cur, temp, ff_wavelet97_coeff, 
linesize, 1 << i, w, h);
-        ff_wavelet97_decompose(vbands, var, temp, s->sqcoeff,         
linesize, 1 << i, w, h);
-        for (int j = 1; j < 4; j++)
-            shrink_band(bands[j], vbands[j], scratch, linesize, 1 << i, w, h);
-        FFSWAP(float *, cur, next);
-        FFSWAP(float *, var, vbands[0]);
-    }
-
-    for (int i = levels - 1; i >= 0; i--) {
-        float *bands[4] = { cur, hbands + 3 * i * plane_size,
-                            hbands + (3 * i + 1) * plane_size, hbands + (3 * i 
+ 2) * plane_size };
-
-        ff_wavelet97_compose(next, bands, temp, linesize, 1 << i, w, h);
-        FFSWAP(float *, cur, next);
-    }
+    transform(ctx, cur, var, wavelet_levels(w, h), lat);
 
     for (int y = 0; y < h; y++)
         for (int x = 0; x < w; x++)
@@ -328,11 +447,13 @@ static void filter_plane_wavelet(VignetteContext *s,
                 av_clip_uint8(cur[y * linesize + x] + bias + 
get_dither_value(s));
 }
 
-static void filter_frame_wavelet(VignetteContext *s, AVFrame *out, const 
AVFrame *in)
+static void filter_frame_wavelet(AVFilterContext *ctx, AVFrame *out, const 
AVFrame *in)
 {
+    VignetteContext *s = ctx->priv;
+
     if (s->desc->flags & AV_PIX_FMT_FLAG_RGB) {
         for (int c = 0; c < 3; c++)
-            filter_plane_wavelet(s, out->data[0] + c, out->linesize[0],
+            filter_plane_wavelet(ctx, out->data[0] + c, out->linesize[0],
                                  in->data[0] + c, in->linesize[0], 3,
                                  in->width, in->height, 0, 0, 0);
         return;
@@ -343,7 +464,7 @@ static void filter_frame_wavelet(VignetteContext *s, 
AVFrame *out, const AVFrame
         const int hsub = chroma ? s->desc->log2_chroma_w : 0;
         const int vsub = chroma ? s->desc->log2_chroma_h : 0;
 
-        filter_plane_wavelet(s, out->data[plane], out->linesize[plane],
+        filter_plane_wavelet(ctx, out->data[plane], out->linesize[plane],
                              in->data[plane], in->linesize[plane], 1,
                              AV_CEIL_RSHIFT(in->width, hsub), 
AV_CEIL_RSHIFT(in->height, vsub),
                              hsub, vsub, chroma ? 127 : 0);
@@ -374,7 +495,7 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
         update_context(s, inlink, in);
 
     if (s->mode == MODE_WAVELET) {
-        filter_frame_wavelet(s, out, in);
+        filter_frame_wavelet(ctx, out, in);
     } else if (s->desc->flags & AV_PIX_FMT_FLAG_RGB) {
         uint8_t       *dst = out->data[0];
         const uint8_t *src = in ->data[0];
@@ -493,7 +614,7 @@ const FFFilter ff_vf_vignette = {
     .p.name        = "vignette",
     .p.description = NULL_IF_CONFIG_SMALL("Make or reverse a vignette 
effect."),
     .p.priv_class  = &vignette_class,
-    .p.flags       = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
+    .p.flags       = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | 
AVFILTER_FLAG_SLICE_THREADS,
     .priv_size     = sizeof(VignetteContext),
     .init          = init,
     .uninit        = uninit,
-- 
2.54.0


From 6f05288208bba115479dfc73edafef7d28b5247d Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Tue, 27 Oct 2026 01:38:33 +0100
Subject: [PATCH 6/6] 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
---
 doc/filters.texi          |  5 +++
 libavfilter/vf_vignette.c | 76 +++++++++++++++++++++++++++++++++++++--
 2 files changed, 78 insertions(+), 3 deletions(-)

diff --git a/doc/filters.texi b/doc/filters.texi
index a6cdc96ad04..12cc6738856 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -26135,6 +26135,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 much less memory, 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 4aec1a909c4..d2e9f4838d8 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;
     int wplane_size;
@@ -100,6 +101,7 @@ static const AVOption vignette_options[] = {
         { "backward", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = MODE_BACKWARD}, 
INT_MIN, INT_MAX, FLAGS, .unit = "mode"},
         { "wavelet",  "backward with noise suppression in the wavelet domain", 
0, AV_OPT_TYPE_CONST, {.i64 = MODE_WAVELET}, INT_MIN, INT_MAX, FLAGS, .unit = 
"mode"},
     { "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 },
     { "eval", "specify when to evaluate expressions", OFFSET(eval_mode), 
AV_OPT_TYPE_INT, {.i64 = EVAL_MODE_INIT}, 0, EVAL_MODE_NB-1, FLAGS, .unit = 
"eval" },
         { "init",  "eval expressions once during initialization", 0, 
AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_INIT},  .flags = FLAGS, .unit = "eval" },
         { "frame", "eval expressions for each frame",             0, 
AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_FRAME}, .flags = FLAGS, .unit = "eval" },
@@ -227,6 +229,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,
@@ -319,6 +323,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;
@@ -359,6 +371,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 };
@@ -386,7 +419,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 int plane_size = s->wplane_size;
@@ -418,6 +451,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 int 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,
@@ -439,7 +506,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++)
@@ -590,7 +660,7 @@ static int config_props(AVFilterLink *inlink)
 
     if (s->mode == MODE_WAVELET) {
         s->wplane_size = 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);
     }
-- 
2.54.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to