PR #25377 opened by Daniel Campos Ramos (danielcamposramos)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25377
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25377.patch

avfilter/stereo3d: correct eye order for interleaved inputs

`stereo3d=icr:sbsl` exchanges the eyes on master 179d0834934.
With an analytic RGB24 input at 128 by 48, every one of the 6,144 output pixels 
is in the wrong eye.
The input has a red left view, a blue right view and a different white box in 
each, with the right view in the even columns.

The column path swaps the output offsets and also chooses source columns from 
the input and output order.
The first patch chooses each source column from the input order and lets the 
output layout place that eye.
It also corrects column reversal and right eye first alternating output.
The family checks found that both mono outputs from right eye first rows select 
the other eye, which the second patch fixes by keeping the proper row offset.

Both patches have an analytic FATE regression that fails before its fix.
The tests use frame MD5 because the exchanged images happen to have the same 
Adler checksum.
GCC and clang each pass all 24 stereo3d FATE tests and 516 independent 
conversion checks at one and four filter threads.
The checks cover both orders of every supported input in RGB24, plus the 
interleaved family in planar RGB, 16 bit planar RGB, RGBA, 48 bit RGB and 64 
bit RGBA.
Checkerboard outputs from column inputs are checked in both orders; 
checkerboard input is not supported by this filter.

The fixed icr conversion matches all 6,144 expected pixels exactly.
The tests generate their inputs without using stereo3d and need no new sample 
download.

AI partners helped produce this work.

Fixes #25376



>From 11b58c219fe5ebacb72320efd708cde1bb7d1658 Mon Sep 17 00:00:00 2001
From: Daniel Campos Ramos <[email protected]>
Date: Fri, 23 Oct 2026 00:10:20 -0300
Subject: [PATCH 1/2] avfilter/stereo3d: preserve eye order for interleaved
 columns

Select the source column from the input eye order and leave the output
placement to the selected output layout. Swapping the configured offsets
as well as the source columns reversed icr packed output. Depending on
the output enum parity also reversed right-first alternating output.

Use the actual input order when interleaving columns again, so icl:icr
exchanges the eyes instead of copying the original column order.

Add an analytic RGB FATE regression with different markers in each eye.
Use frame MD5 because the exchanged views have the same Adler checksum.
The reference is generated from independently packed left and right
images, and fails on unpatched master.

Signed-off-by: Daniel Campos Ramos <[email protected]>
Assisted-by: gpt-6-astra via Codex
---
 libavfilter/vf_stereo3d.c               | 19 +++++--------------
 tests/fate/filter-video.mak             |  9 +++++++++
 tests/filtergraphs/stereo3d-icr         |  8 ++++++++
 tests/ref/fate/filter-stereo3d-icr-sbsl | 10 ++++++++++
 4 files changed, 32 insertions(+), 14 deletions(-)
 create mode 100644 tests/filtergraphs/stereo3d-icr
 create mode 100644 tests/ref/fate/filter-stereo3d-icr-sbsl

diff --git a/libavfilter/vf_stereo3d.c b/libavfilter/vf_stereo3d.c
index 15d6f31c11f..b53eb5ae310 100644
--- a/libavfilter/vf_stereo3d.c
+++ b/libavfilter/vf_stereo3d.c
@@ -576,17 +576,6 @@ static int config_output(AVFilterLink *outlink)
         return AVERROR(EINVAL);
     }
 
-    if (s->in.format == INTERLEAVE_COLS_LR || s->in.format == 
INTERLEAVE_COLS_RL) {
-        if ((s->in.format & 1) != (s->out.format & 1)) {
-            FFSWAP(int, s->in.row_left,   s->in.row_right);
-            FFSWAP(int, s->in.off_lstep,  s->in.off_rstep);
-            FFSWAP(int, s->in.off_left,   s->in.off_right);
-            FFSWAP(int, s->out.row_left,  s->out.row_right);
-            FFSWAP(int, s->out.off_lstep, s->out.off_rstep);
-            FFSWAP(int, s->out.off_left,  s->out.off_right);
-        }
-    }
-
     outlink->w = s->out.width;
     outlink->h = s->out.height;
     ol->frame_rate = fps;
@@ -860,7 +849,7 @@ copy:
         if (s->in.format == INTERLEAVE_COLS_LR ||
             s->in.format == INTERLEAVE_COLS_RL) {
             for (i = 0; i < s->nb_planes; i++) {
-                int d = (s->in.format & 1) != (s->out.format & 1);
+                int d = s->in.format == INTERLEAVE_COLS_RL;
 
                 interleave_cols_to_any(s, out_off_left,  i, ileft,  oleft,   
d);
                 interleave_cols_to_any(s, out_off_right, i, iright, oright, 
!d);
@@ -1021,12 +1010,14 @@ copy:
         for (i = 0; i < s->nb_planes; i++) {
             const int d = (s->in.format == INTERLEAVE_COLS_LR || s->in.format 
== INTERLEAVE_COLS_RL);
             const int m = 1 + d;
+            const int l = s->in.format == INTERLEAVE_COLS_RL;
+            const int r = s->in.format == INTERLEAVE_COLS_LR;
             int x, y;
 
             for (y = 0; y < s->pheight[i]; y++) {
                 uint8_t *dst = out->data[i] + out->linesize[i] * y;
-                uint8_t *left = ileft->data[i] + ileft->linesize[i] * y * 
s->in.row_step + s->in_off_left[i] + d * s->pixstep[i];
-                uint8_t *right = iright->data[i] + iright->linesize[i] * y * 
s->in.row_step + s->in_off_right[i];
+                uint8_t *left = ileft->data[i] + ileft->linesize[i] * y * 
s->in.row_step + s->in_off_left[i] + l * s->pixstep[i];
+                uint8_t *right = iright->data[i] + iright->linesize[i] * y * 
s->in.row_step + s->in_off_right[i] + r * s->pixstep[i];
                 int p, b;
 
                 if (s->out.format == INTERLEAVE_COLS_LR)
diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak
index 4a065ec0320..9f9e6713ed1 100644
--- a/tests/fate/filter-video.mak
+++ b/tests/fate/filter-video.mak
@@ -733,6 +733,15 @@ FATE_STEREO3D-$(call FILTERDEMDEC, SCALE STEREO3D, IMAGE2, 
PGMYUV) += $(FATE_STE
 fate-filter-stereo3d: $(FATE_STEREO3D-yes)
 FATE_FILTER_VSYNTH-yes += $(FATE_STEREO3D-yes)
 
+# Analytic right-first columns: red left view, blue right view, different 
white boxes.
+FATE_STEREO3D_ICR-$(call ALLYES, NULLSRC_FILTER GEQ_FILTER FORMAT_FILTER 
SCALE_FILTER \
+                              STEREO3D_FILTER RAWVIDEO_ENCODER FRAMEMD5_MUXER \
+                              FILE_PROTOCOL PIPE_PROTOCOL) += 
fate-filter-stereo3d-icr-sbsl
+fate-filter-stereo3d-icr-sbsl: tests/data/filtergraphs/stereo3d-icr
+fate-filter-stereo3d-icr-sbsl: CMD = framemd5 -/filter_complex 
$(TARGET_PATH)/tests/data/filtergraphs/stereo3d-icr -frames:v 1 -pix_fmt rgb24
+FATE_FILTER-yes += $(FATE_STEREO3D_ICR-yes)
+fate-filter-stereo3d: $(FATE_STEREO3D_ICR-yes)
+
 FATE_FILTER_VSYNTH_VIDEO_FILTER-$(call ALLYES, SCALE_FILTER THUMBNAIL_FILTER) 
+= fate-filter-thumbnail
 fate-filter-thumbnail: CMD = video_filter "scale,thumbnail=10"
 
diff --git a/tests/filtergraphs/stereo3d-icr b/tests/filtergraphs/stereo3d-icr
new file mode 100644
index 00000000000..d9d063aae7a
--- /dev/null
+++ b/tests/filtergraphs/stereo3d-icr
@@ -0,0 +1,8 @@
+nullsrc=s=128x48:r=1,
+format=gbrp,
+geq=r='if(mod(X,2),255,255*between(X,64,79)*between(Y,24,31))':
+    
g='if(mod(X,2),255*between(X,16,31)*between(Y,8,15),255*between(X,64,79)*between(Y,24,31))':
+    b='if(mod(X,2),255*between(X,16,31)*between(Y,8,15),255)',
+scale,
+format=rgb24,
+stereo3d=icr:sbsl
diff --git a/tests/ref/fate/filter-stereo3d-icr-sbsl 
b/tests/ref/fate/filter-stereo3d-icr-sbsl
new file mode 100644
index 00000000000..0f830b7cc29
--- /dev/null
+++ b/tests/ref/fate/filter-stereo3d-icr-sbsl
@@ -0,0 +1,10 @@
+#format: frame checksums
+#version: 2
+#hash: MD5
+#tb 0: 1/1
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 128x48
+#sar 0: 1/1
+#stream#, dts,        pts, duration,     size, hash
+0,          0,          0,        1,    18432, 04d7d564965d41f85a201062e55ee9de
-- 
2.54.0


>From d4be831f8196c887d06b7057b677e13fba978339 Mon Sep 17 00:00:00 2001
From: Daniel Campos Ramos <[email protected]>
Date: Fri, 23 Oct 2026 00:13:40 -0300
Subject: [PATCH 2/2] avfilter/stereo3d: select the correct mono view from
 interleaved rows

Keep the configured left row offset for mono-left output and copy the
right row offset for mono-right output. Swapping offsets only for one
input order selected the other eye for both irr mono outputs.

Add a FATE regression that independently generates right-first rows,
extracts both mono views and joins them in left-first order. It shares
the analytic reference of the column test and fails before this change.

Signed-off-by: Daniel Campos Ramos <[email protected]>
Assisted-by: gpt-6-astra via Codex
---
 libavfilter/vf_stereo3d.c            | 11 +++--------
 tests/fate/filter-video.mak          |  9 +++++++++
 tests/filtergraphs/stereo3d-irr-mono | 11 +++++++++++
 3 files changed, 23 insertions(+), 8 deletions(-)
 create mode 100644 tests/filtergraphs/stereo3d-irr-mono

diff --git a/libavfilter/vf_stereo3d.c b/libavfilter/vf_stereo3d.c
index b53eb5ae310..81eea9e1f27 100644
--- a/libavfilter/vf_stereo3d.c
+++ b/libavfilter/vf_stereo3d.c
@@ -549,16 +549,11 @@ static int config_output(AVFilterLink *outlink)
         s->out.off_lstep = 1;
         break;
     case MONO_R:
-        if (s->in.format != INTERLEAVE_COLS_LR) {
-            s->in.off_left = s->in.off_right;
-            s->in.row_left = s->in.row_right;
-        }
-        if (s->in.format == INTERLEAVE_ROWS_LR)
-            FFSWAP(int, s->in.off_lstep, s->in.off_rstep);
+        s->in.off_left  = s->in.off_right;
+        s->in.row_left  = s->in.row_right;
+        s->in.off_lstep = s->in.off_rstep;
         break;
     case MONO_L:
-        if (s->in.format == INTERLEAVE_ROWS_RL)
-            FFSWAP(int, s->in.off_lstep, s->in.off_rstep);
         break;
     case ALTERNATING_RL:
     case ALTERNATING_LR:
diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak
index 9f9e6713ed1..6f9b9ac7e24 100644
--- a/tests/fate/filter-video.mak
+++ b/tests/fate/filter-video.mak
@@ -742,6 +742,15 @@ fate-filter-stereo3d-icr-sbsl: CMD = framemd5 
-/filter_complex $(TARGET_PATH)/te
 FATE_FILTER-yes += $(FATE_STEREO3D_ICR-yes)
 fate-filter-stereo3d: $(FATE_STEREO3D_ICR-yes)
 
+FATE_STEREO3D_IRR-$(call ALLYES, NULLSRC_FILTER GEQ_FILTER FORMAT_FILTER 
SCALE_FILTER \
+                              SPLIT_FILTER HSTACK_FILTER STEREO3D_FILTER \
+                              RAWVIDEO_ENCODER FRAMEMD5_MUXER FILE_PROTOCOL 
PIPE_PROTOCOL) += fate-filter-stereo3d-irr-mono
+fate-filter-stereo3d-irr-mono: tests/data/filtergraphs/stereo3d-irr-mono
+fate-filter-stereo3d-irr-mono: CMD = framemd5 -/filter_complex 
$(TARGET_PATH)/tests/data/filtergraphs/stereo3d-irr-mono -frames:v 1 -pix_fmt 
rgb24
+fate-filter-stereo3d-irr-mono: REF = 
$(SRC_PATH)/tests/ref/fate/filter-stereo3d-icr-sbsl
+FATE_FILTER-yes += $(FATE_STEREO3D_IRR-yes)
+fate-filter-stereo3d: $(FATE_STEREO3D_IRR-yes)
+
 FATE_FILTER_VSYNTH_VIDEO_FILTER-$(call ALLYES, SCALE_FILTER THUMBNAIL_FILTER) 
+= fate-filter-thumbnail
 fate-filter-thumbnail: CMD = video_filter "scale,thumbnail=10"
 
diff --git a/tests/filtergraphs/stereo3d-irr-mono 
b/tests/filtergraphs/stereo3d-irr-mono
new file mode 100644
index 00000000000..f2850cc1c0a
--- /dev/null
+++ b/tests/filtergraphs/stereo3d-irr-mono
@@ -0,0 +1,11 @@
+nullsrc=s=64x96:r=1,
+format=gbrp,
+geq=r='if(mod(Y,2),255,255*between(X,32,39)*between(Y,48,63))':
+    
g='if(mod(Y,2),255*between(X,8,15)*between(Y,16,31),255*between(X,32,39)*between(Y,48,63))':
+    b='if(mod(Y,2),255*between(X,8,15)*between(Y,16,31),255)',
+scale,
+format=rgb24,
+split[left][right];
+[left]stereo3d=irr:ml[l];
+[right]stereo3d=irr:mr[r];
+[l][r]hstack
-- 
2.54.0

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

Reply via email to