From 734743bb89e158727bf0fa0f7a7fd2dd163ce40f Mon Sep 17 00:00:00 2001 From: Michael Niedermayer Date: Tue, 27 Oct 2026 01:38:33 +0100 Subject: [PATCH] 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 | 11 ++++-- libavfilter/vf_vignette.c | 76 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 81 insertions(+), 6 deletions(-) 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); }