PR #25736 opened by Kilian (lusoris) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25736 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25736.patch
This carries the follow-up commit from the `fairy/pr25631-runtime-followup` branch, which was proposed in the review of pull request 25631, as its own pull request. The commit is unchanged, and authorship stays with the branch. vf_normalize keeps scaled copies of `blackpt` and `whitept`. The generic command handler updates the option values but not those copies, so point changes sent as runtime commands (for example with `sendcmd`) have no effect on the output. The commit moves the point scaling into one helper used by both configuration and a `process_command()` wrapper, and adds a three-frame FATE test that changes both points at runtime. The branch is based on pull request 25631 and does not apply to master without it, so this pull request contains that one's commits too. Only the last commit is new here. ## Tests Measured on this branch with a minimal build (normalize, testsrc, sendcmd and the FATE dependencies): | Tree | fate-filter-normalize-commands | normalize-range, white-rgb24, white-gbrp16le | |---|---|---| | this branch | pass | pass | | this branch, `CPUFLAGS=0` | pass | pass | | this branch with the vf_normalize.c change reverted | fails: frames 1 and 2 differ from the reference (0xcfec60ad, 0x887c60ad instead of 0xd07c2e1e, 0x817c7fd3) | pass | `make fate-source` passes. >From 43dea45ee0e5b8e91b2133aec859179bacb79d95 Mon Sep 17 00:00:00 2001 From: lusoris <[email protected]> Date: Fri, 30 Oct 2026 23:50:54 +0100 Subject: [PATCH 1/2] avfilter/vf_normalize: fix white and black point scaling The 8-bit black and white point options are scaled to the input bit depth by bit replication, so that the low bits repeat the high bits and 255 maps to the full-scale value at every depth. The expression introduced in c7ece8e26f masks a single bit of the point value instead of replicating the high bits, which is wrong at both ends of the range: a white point of 255 becomes 256 at 8 bits and 65280 at 16 bits instead of 255 and 65535, and at 8-bit depth any odd point value is one too high. With blackpt=white and whitept=black on a black frame the output is 0 at 8 bits instead of 255, and 65280 at 16 bits instead of 65535. Ordinary input lands one off the requested range, for example 128..130 for a requested 127..129. Shift by 16 - depth so the 8-bit point is replicated into the high bits of the target depth: 255 maps to 255 at 8 bits and to 65535 at 16 bits. Add FATE tests for the two ends of the supported depth range. Add a non-constant range test that covers white-point scaling. Signed-off-by: lusoris <[email protected]> --- libavfilter/vf_normalize.c | 4 ++-- tests/fate/filter-video.mak | 8 ++++++++ tests/ref/fate/filter-normalize-range | 6 ++++++ tests/ref/fate/filter-normalize-white-gbrp16le | 6 ++++++ tests/ref/fate/filter-normalize-white-rgb24 | 6 ++++++ 5 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 tests/ref/fate/filter-normalize-range create mode 100644 tests/ref/fate/filter-normalize-white-gbrp16le create mode 100644 tests/ref/fate/filter-normalize-white-rgb24 diff --git a/libavfilter/vf_normalize.c b/libavfilter/vf_normalize.c index 6d32bc85fcb..937c802f77a 100644 --- a/libavfilter/vf_normalize.c +++ b/libavfilter/vf_normalize.c @@ -437,8 +437,8 @@ static int config_input(AVFilterLink *inlink) for (c = 0; c < 3; c++) { s->min[c].history = s->history_mem + (c*2) * s->history_len; s->max[c].history = s->history_mem + (c*2+1) * s->history_len; - s->sblackpt[c] = scale * s->blackpt[c] + (s->blackpt[c] & (1 << (s->depth - 8))); - s->swhitept[c] = scale * s->whitept[c] + (s->whitept[c] & (1 << (s->depth - 8))); + s->sblackpt[c] = scale * s->blackpt[c] + (s->blackpt[c] >> (16 - s->depth)); + s->swhitept[c] = scale * s->whitept[c] + (s->whitept[c] >> (16 - s->depth)); } planar = desc->flags & AV_PIX_FMT_FLAG_PLANAR; diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak index 95254ff3fcf..7093ee42274 100644 --- a/tests/fate/filter-video.mak +++ b/tests/fate/filter-video.mak @@ -181,6 +181,14 @@ FATE_FILTER-$(call FILTERFRAMECRC, TESTSRC FORMAT CONCAT SCALE, LAVFI_INDEV FILE fate-filter-lavd-scalenorm: tests/data/filtergraphs/scalenorm fate-filter-lavd-scalenorm: CMD = framecrc -f lavfi -graph_file $(TARGET_PATH)/tests/data/filtergraphs/scalenorm -i dummy +FATE_FILTER-$(call FILTERFRAMECRC, COLOR FORMAT NORMALIZE) += fate-filter-normalize-white-rgb24 +fate-filter-normalize-white-rgb24: CMD = framecrc -flags bitexact -lavfi color=c=black:s=16x16:r=1:d=1,format=rgb24,normalize=blackpt=white:whitept=black +FATE_FILTER-$(call FILTERFRAMECRC, COLOR FORMAT NORMALIZE SCALE) += fate-filter-normalize-white-gbrp16le +fate-filter-normalize-white-gbrp16le: CMD = framecrc -flags bitexact -lavfi color=c=black:s=16x16:r=1:d=1,format=rgb24,scale,format=gbrp16,normalize=blackpt=white:whitept=black,scale -pix_fmt gbrp16le + +FATE_FILTER-$(call FILTERFRAMECRC, TESTSRC NORMALIZE) += fate-filter-normalize-range +fate-filter-normalize-range: CMD = framecrc -flags bitexact -lavfi testsrc=s=16x16:r=1:d=1,normalize=blackpt=0x7f7f7f:whitept=0x818181 + FATE_FILTER-$(call FILTERFRAMECRC, COLOR FORMAT SCALE CROP) += fate-filter-scale-fast-bilinear-wide-edge fate-filter-scale-fast-bilinear-wide-edge: CMD = framecrc -flags bitexact -lavfi color=c=red:s=40000x1:r=1:d=1,format=yuv444p,scale=40032:1:flags=fast_bilinear,crop=1:1:40031:0 -frames:v 1 diff --git a/tests/ref/fate/filter-normalize-range b/tests/ref/fate/filter-normalize-range new file mode 100644 index 00000000000..386fefa955d --- /dev/null +++ b/tests/ref/fate/filter-normalize-range @@ -0,0 +1,6 @@ +#tb 0: 1/1 +#media_type 0: video +#codec_id 0: rawvideo +#dimensions 0: 16x16 +#sar 0: 1/1 +0, 0, 0, 1, 768, 0x817c7fd3 diff --git a/tests/ref/fate/filter-normalize-white-gbrp16le b/tests/ref/fate/filter-normalize-white-gbrp16le new file mode 100644 index 00000000000..9789e180f25 --- /dev/null +++ b/tests/ref/fate/filter-normalize-white-gbrp16le @@ -0,0 +1,6 @@ +#tb 0: 1/1 +#media_type 0: video +#codec_id 0: rawvideo +#dimensions 0: 16x16 +#sar 0: 1/1 +0, 0, 0, 1, 1536, 0x0a2efa4b diff --git a/tests/ref/fate/filter-normalize-white-rgb24 b/tests/ref/fate/filter-normalize-white-rgb24 new file mode 100644 index 00000000000..c6eadac969c --- /dev/null +++ b/tests/ref/fate/filter-normalize-white-rgb24 @@ -0,0 +1,6 @@ +#tb 0: 1/1 +#media_type 0: video +#codec_id 0: rawvideo +#dimensions 0: 16x16 +#sar 0: 1/1 +0, 0, 0, 1, 768, 0x41d3fd1e -- 2.54.0 >From 78b5efde8aeef666efc9c843f011e60c6581704e Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Tue, 3 Nov 2026 12:52:30 +0000 Subject: [PATCH 2/2] avfilter/vf_normalize: refresh points after runtime commands The generic command handler updates blackpt and whitept but leaves their scaled copies unchanged. Share point scaling with configuration and refresh the copies after successful runtime commands, once the depth is known. Add a generated three-frame FATE test covering both point commands. This is a separate follow-up to the point-scaling fix; the stale cache predates it. Assisted-by: Fairy --- libavfilter/vf_normalize.c | 32 ++++++++++++++++++++---- tests/fate/filter-video.mak | 3 +++ tests/ref/fate/filter-normalize-commands | 8 ++++++ 3 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 tests/ref/fate/filter-normalize-commands diff --git a/libavfilter/vf_normalize.c b/libavfilter/vf_normalize.c index 937c802f77a..324e7497dbd 100644 --- a/libavfilter/vf_normalize.c +++ b/libavfilter/vf_normalize.c @@ -406,6 +406,29 @@ static const enum AVPixelFormat pixel_fmts[] = { AV_PIX_FMT_NONE }; +static void scale_points(NormalizeContext *s) +{ + const int scale = 1 << (s->depth - 8); + + for (int c = 0; c < 3; c++) { + s->sblackpt[c] = scale * s->blackpt[c] + (s->blackpt[c] >> (16 - s->depth)); + s->swhitept[c] = scale * s->whitept[c] + (s->whitept[c] >> (16 - s->depth)); + } +} + +static int process_command(AVFilterContext *ctx, const char *cmd, const char *args, + char *res, int res_len, int flags) +{ + NormalizeContext *s = ctx->priv; + int ret = ff_filter_process_command(ctx, cmd, args, res, res_len, flags); + + if (ret < 0) + return ret; + if (s->depth >= 8 && s->depth <= 16) + scale_points(s); + return 0; +} + // At this point we know the pixel format used for both input and output. We // can also access the frame rate of the input video and allocate some memory // appropriately @@ -414,11 +437,10 @@ static int config_input(AVFilterLink *inlink) NormalizeContext *s = inlink->dst->priv; // Store offsets to R,G,B,A bytes respectively in each pixel const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); - int c, planar, scale; + int c, planar; ff_fill_rgba_map(s->co, inlink->format); s->depth = desc->comp[0].depth; - scale = 1 << (s->depth - 8); s->num_components = desc->nb_components; s->step = av_get_padded_bits_per_pixel(desc) >> (3 + (s->depth > 8)); // Convert smoothing value to history_len (a count of frames to average, @@ -437,10 +459,10 @@ static int config_input(AVFilterLink *inlink) for (c = 0; c < 3; c++) { s->min[c].history = s->history_mem + (c*2) * s->history_len; s->max[c].history = s->history_mem + (c*2+1) * s->history_len; - s->sblackpt[c] = scale * s->blackpt[c] + (s->blackpt[c] >> (16 - s->depth)); - s->swhitept[c] = scale * s->whitept[c] + (s->whitept[c] >> (16 - s->depth)); } + scale_points(s); + planar = desc->flags & AV_PIX_FMT_FLAG_PLANAR; if (s->depth <= 8) { @@ -520,5 +542,5 @@ const FFFilter ff_vf_normalize = { FILTER_INPUTS(inputs), FILTER_OUTPUTS(ff_video_default_filterpad), FILTER_PIXFMTS_ARRAY(pixel_fmts), - .process_command = ff_filter_process_command, + .process_command = process_command, }; diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak index 7093ee42274..c819956391a 100644 --- a/tests/fate/filter-video.mak +++ b/tests/fate/filter-video.mak @@ -181,6 +181,9 @@ FATE_FILTER-$(call FILTERFRAMECRC, TESTSRC FORMAT CONCAT SCALE, LAVFI_INDEV FILE fate-filter-lavd-scalenorm: tests/data/filtergraphs/scalenorm fate-filter-lavd-scalenorm: CMD = framecrc -f lavfi -graph_file $(TARGET_PATH)/tests/data/filtergraphs/scalenorm -i dummy +FATE_FILTER-$(call FILTERFRAMECRC, TESTSRC SENDCMD NORMALIZE) += fate-filter-normalize-commands +fate-filter-normalize-commands: CMD = run $(FFMPEG) -nostdin -noauto_conversion_filters -cpuflags $$cpuflags -flags bitexact -lavfi "testsrc=s=16x16:r=1:d=3,sendcmd=c=1 normalize blackpt 0x7f7f7f\\;2 normalize whitept 0x818181,normalize" -bitexact -f framecrc - + FATE_FILTER-$(call FILTERFRAMECRC, COLOR FORMAT NORMALIZE) += fate-filter-normalize-white-rgb24 fate-filter-normalize-white-rgb24: CMD = framecrc -flags bitexact -lavfi color=c=black:s=16x16:r=1:d=1,format=rgb24,normalize=blackpt=white:whitept=black FATE_FILTER-$(call FILTERFRAMECRC, COLOR FORMAT NORMALIZE SCALE) += fate-filter-normalize-white-gbrp16le diff --git a/tests/ref/fate/filter-normalize-commands b/tests/ref/fate/filter-normalize-commands new file mode 100644 index 00000000000..833e7d03baf --- /dev/null +++ b/tests/ref/fate/filter-normalize-commands @@ -0,0 +1,8 @@ +#tb 0: 1/1 +#media_type 0: video +#codec_id 0: rawvideo +#dimensions 0: 16x16 +#sar 0: 1/1 +0, 0, 0, 1, 768, 0x887c60ad +0, 1, 1, 1, 768, 0xd07c2e1e +0, 2, 2, 1, 768, 0x817c7fd3 -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
