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]

Reply via email to