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);
}