PR #25309 opened by Marti2203
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25309
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25309.patch

`reflecty()` in vf_v360.c maps a row below the bottom edge with `2 * h - 1 - 
y`, so row h becomes row h - 1, but a row above the top edge with `y = -y`, so 
row -1 becomes row 1 and row 0 is skipped. The interpolators with a 4x4 window 
(cubic, lanczos, spline16, gaussian, mitchell) sample row `vi - 1`, so near the 
top pole they read the wrong row, while the bottom pole is handled correctly. 
For an equirect input that is symmetric about the equator, the output is 
therefore not symmetric:

```
# 128x64 gray equirect: rows 0 and 63 = 255, rows 1 and 62 = 0, rest 128
ffmpeg -f rawvideo -pix_fmt gray -s 128x64 -i in.gray -vf 
v360=e:e:interp=cubic:w=128:h=100 -f rawvideo out.gray
```

(row r, row h-1-r) of the output for r = 0..2, and the largest difference 
between the output and its vertical flip:

```
master   cubic    [(187, 172), (12, 9), (59, 59)]   max diff 16
master   lanczos  [(200, 178), (5, 5), (63, 63)]    max diff 22
master   spline16 [(194, 174), (9, 7), (58, 58)]    max diff 20
patched  cubic    [(172, 172), (9, 9), (59, 59)]    max diff 1
patched  lanczos  [(178, 178), (5, 5), (63, 63)]    max diff 1
patched  spline16 [(174, 174), (7, 7), (58, 58)]    max diff 1
```

Linear interpolation is not affected (max diff 0 before and after). The patch 
reflects row -1 to row 0 (`y = -y - 1`), matching the bottom edge. I also 
checked the horizontal helpers: `ereflectx()` shifts by half the width at both 
poles and `reflectx()` mirrors with `w - 1 - x`, both symmetric, and a 
left-right symmetric input gives a left-right symmetric output (max diff 1) on 
master already, so they are left alone. `reflecty()` is also used by the 
tetrahedron and tspyramid inputs, which get the same correction.

The second commit adds `fate-filter-v360-reflecty`: a vertically symmetric 
frame (geq pattern stacked with its vflip) goes through 
`v360=e:e:interp=cubic`, and the test outputs the difference between the result 
and its vertical flip, thresholded at 2. The threshold absorbs the ±1 from the 
float coordinates, so the expected output is a black frame and does not depend 
on libm details; on master 232 pixels are above it. The test fails on master 
and passes with the fix (THREADS=1 and 4, gcc and clang builds); `make 
fate-vfilter` (503 tests without samples) passes. There was no FATE coverage 
for v360 before. I could not test the x86 SIMD remap functions (no nasm here), 
but they only consume the index tables this patch changes.

This issue was found by an LLM-Enhanced Static Analysis Engine and verified 
manually.

🤖 Generated with [Claude Code](https://claude.com/claude-code)



From 97b520ed2808750516b59c60ee2f20dd1a6dc19d Mon Sep 17 00:00:00 2001
From: Martin Mirchev <[email protected]>
Date: Wed, 21 Oct 2026 19:08:52 +0800
Subject: [PATCH 1/2] avfilter/vf_v360: fix reflecty() at the top edge

reflecty() maps row h to h - 1 at the bottom edge but row -1 to row 1
at the top edge, skipping row 0. Interpolators with a 4x4 window (cubic,
lanczos, spline16, gaussian, mitchell) sample row -1 near the top pole,
so they read the wrong row there, and a vertically symmetric equirect
input gives an output that is not vertically symmetric.

Reflect row -1 to row 0, like the bottom edge. ereflectx() and
reflectx() are already symmetric.

Signed-off-by: Martin Mirchev <[email protected]>
---
 libavfilter/vf_v360.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavfilter/vf_v360.c b/libavfilter/vf_v360.c
index 367c217a799..b7671c5a6bf 100644
--- a/libavfilter/vf_v360.c
+++ b/libavfilter/vf_v360.c
@@ -771,7 +771,7 @@ static inline int mod(int a, int b)
 static inline int reflecty(int y, int h)
 {
     if (y < 0) {
-        y = -y;
+        y = -y - 1;
     } else if (y >= h) {
         y = 2 * h - 1 - y;
     }
-- 
2.54.0


From a240f261e99f47753acf89422bf37d4dc97429b9 Mon Sep 17 00:00:00 2001
From: Martin Mirchev <[email protected]>
Date: Wed, 21 Oct 2026 19:09:09 +0800
Subject: [PATCH 2/2] tests/fate/filter-video: add a test for v360 reflection
 at the poles

Feed a vertically symmetric equirect frame through v360 with cubic
interpolation and output the difference between the result and its
vertical flip, thresholded at 2 to absorb the rounding of the float
coordinates. The output must be black; before the previous commit the
rows near the top pole differed from the bottom ones.

Signed-off-by: Martin Mirchev <[email protected]>
---
 tests/fate/filter-video.mak         | 4 ++++
 tests/filtergraphs/v360-reflecty    | 5 +++++
 tests/ref/fate/filter-v360-reflecty | 6 ++++++
 3 files changed, 15 insertions(+)
 create mode 100644 tests/filtergraphs/v360-reflecty
 create mode 100644 tests/ref/fate/filter-v360-reflecty

diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak
index 4a065ec0320..dc4240a6605 100644
--- a/tests/fate/filter-video.mak
+++ b/tests/fate/filter-video.mak
@@ -430,6 +430,10 @@ fate-filter-untile: CMD = framecrc -lavfi 
testsrc2=d=1:r=2,untile=2x2
 FATE_FILTER-$(call FILTERFRAMECRC, TESTSRC2 UNTILE) += 
fate-filter-untile-yuv422p
 fate-filter-untile-yuv422p: CMD = framecrc -lavfi 
testsrc2=d=1:r=2,format=yuv422p,untile=2x2
 
+FATE_FILTER-$(call FILTERFRAMECRC, NULLSRC FORMAT GEQ SPLIT VFLIP VSTACK V360 
BLEND LUT, FILE_PROTOCOL) += fate-filter-v360-reflecty
+fate-filter-v360-reflecty: tests/data/filtergraphs/v360-reflecty
+fate-filter-v360-reflecty: CMD = framecrc -/filter_complex 
$(TARGET_PATH)/tests/data/filtergraphs/v360-reflecty
+
 FATE_FILTER_VSYNTH_PGMYUV-$(CONFIG_UNSHARP_FILTER) += fate-filter-unsharp
 fate-filter-unsharp: CMD = framecrc -c:v pgmyuv -i $(SRC) -vf 
unsharp=11:11:-1.5:11:11:-1.5
 
diff --git a/tests/filtergraphs/v360-reflecty b/tests/filtergraphs/v360-reflecty
new file mode 100644
index 00000000000..959dd0d4307
--- /dev/null
+++ b/tests/filtergraphs/v360-reflecty
@@ -0,0 +1,5 @@
+nullsrc=s=128x32:r=1:d=1, format=gray, geq=lum='mod(X*37+Y*101,256)', split 
[top][b];
+[b] vflip [bottom];
+[top][bottom] vstack, v360=e:e:interp=cubic:w=128:h=100, split [out][o];
+[o] vflip [flipped];
+[out][flipped] blend=all_mode=difference, lut=c0='gt(val,2)*255'
diff --git a/tests/ref/fate/filter-v360-reflecty 
b/tests/ref/fate/filter-v360-reflecty
new file mode 100644
index 00000000000..40acda689a5
--- /dev/null
+++ b/tests/ref/fate/filter-v360-reflecty
@@ -0,0 +1,6 @@
+#tb 0: 1/1
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 128x100
+#sar 0: 1/1
+0,          0,          0,        1,    12800, 0x00000000
-- 
2.54.0

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

Reply via email to