PR #25631 opened by Kilian (lusoris)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25631
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25631.patch

The normalize filter scales its 8-bit black and white point options to the 
input bit depth by bit replication: the low bits repeat the high bits so that 
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 requested 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 `normalize=blackpt=white: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.

The fix shifts 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.

Two FATE tests are added for the two ends of the supported depth range (rgb24 
and gbrp16le). Both fail on current master and pass with the fix.

Measured on master before and after:

- `normalize=blackpt=white:whitept=black` on black rgb24: 0 -> 255
- same on gbrp16le: 65280 -> 65535
- requested 127..129 on rgb24: 128..130 -> 127..129



>From 52d54a07b48b72e7fff9f817b1f945ebb0d15a0a Mon Sep 17 00:00:00 2001
From: lusoris <[email protected]>
Date: Fri, 30 Oct 2026 23:50:54 +0100
Subject: [PATCH] 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.

Signed-off-by: lusoris <[email protected]>
---
 libavfilter/vf_normalize.c                     | 4 ++--
 tests/fate/filter-video.mak                    | 4 ++++
 tests/ref/fate/filter-normalize-white-gbrp16le | 6 ++++++
 tests/ref/fate/filter-normalize-white-rgb24    | 6 ++++++
 4 files changed, 18 insertions(+), 2 deletions(-)
 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..9c32dd1149f 100644
--- a/tests/fate/filter-video.mak
+++ b/tests/fate/filter-video.mak
@@ -181,6 +181,10 @@ 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) += $(addprefix 
fate-filter-normalize-white-, rgb24 gbrp16le)
+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-normalize-white-gbrp16le: CMD = framecrc -flags bitexact -lavfi 
color=c=black:s=16x16:r=1:d=1,format=gbrp16le,normalize=blackpt=white:whitept=black
+
 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-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

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

Reply via email to