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

The C YUV420P/YUV422P/YUVA420P to RGB converters in libswscale/yuv2rgb.c 
process each row in blocks of 8, 4 and 2 pixels, so with an odd output width 
the last pixel of every row is never written and keeps whatever was in the 
destination buffer.

The unscaled path selection only rejects odd heights, and the ARM and AArch64 
NEON versions need widths divisible by 16, so on those platforms odd widths 
always reach this code. x86 SSSE3 rounds the row up to 8 pixels and so is 
unaffected.

This shows up in Kodi as a line of random pixels on the right edge of posters 
on Raspberry Pi and other ARM devices (https://github.com/xbmc/xbmc/pull/30305 
works around it with SWS_ACCURATE_RND).

This patch adds a single pixel stage after the 2 pixel stage, covering all 
output formats of these converters.

An alternative would be to reject odd widths in the unscaled path selection, as 
is done for odd heights, but the generic path is about 10x slower for these 
sizes. Pi 4, yuv420p -> bgra, unscaled, per frame:

| size | this patch | generic path |
|---|---|---|
| 379x540 | 0.85 ms | 8.8 ms |
| 1919x1080 | 14 ms | 91 ms |

Tested:
- converting at odd width W with the destination pre-filled with two different 
patterns gives identical output, and matches the W+1 conversion in the first W 
pixels (for dithered formats, at widths where the dither phase lines up), for 
all formats that use these converters, with av_force_cpu_flags(0)
- checkasm sw_yuv2rgb and fate-filter-pixfmts pass



>From 6e5ac96b9d7a8ff2ea2bdf71f7d9a85ba1f89a2e Mon Sep 17 00:00:00 2001
From: Dom Cobley <[email protected]>
Date: Tue, 3 Nov 2026 16:53:22 +0000
Subject: [PATCH] swscale/yuv2rgb: write the last pixel of odd width rows

The C YUV420P/YUV422P/YUVA420P to RGB converters process each row in
blocks of 8, 4 and 2 pixels, so for an odd output width the final
pixel of every row was never written and kept whatever was in the
destination buffer.

The unscaled path selection only rejects odd heights, and the ARM and
AArch64 NEON versions require widths divisible by 16, so on those
platforms odd widths always reach this code. x86 SSSE3 rounds the row
up to 8 pixels and so is unaffected.

Add a single pixel stage after the 2 pixel stage.

Signed-off-by: Dom Cobley <[email protected]>
---
 libswscale/yuv2rgb.c | 99 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 99 insertions(+)

diff --git a/libswscale/yuv2rgb.c b/libswscale/yuv2rgb.c
index a430a110db7..0b820050b7b 100644
--- a/libswscale/yuv2rgb.c
+++ b/libswscale/yuv2rgb.c
@@ -78,6 +78,10 @@ const int *sws_getCoefficients(int colorspace)
     Y                  = py_##l[2 * i + 1];         \
     dst_##l[2 * i + 1] = r[Y] + g[Y] + b[Y];
 
+#define PUTRGB_ODD(l, i, abase)                     \
+    Y                  = py_##l[2 * i];             \
+    dst_##l[2 * i]     = r[Y] + g[Y] + b[Y];
+
 #define PUTRGB24(l, i, abase)                       \
     Y                  = py_##l[2 * i];             \
     dst_##l[6 * i + 0] = r[Y];                      \
@@ -88,6 +92,12 @@ const int *sws_getCoefficients(int colorspace)
     dst_##l[6 * i + 4] = g[Y];                      \
     dst_##l[6 * i + 5] = b[Y];
 
+#define PUTRGB24_ODD(l, i, abase)                   \
+    Y                  = py_##l[2 * i];             \
+    dst_##l[6 * i + 0] = r[Y];                      \
+    dst_##l[6 * i + 1] = g[Y];                      \
+    dst_##l[6 * i + 2] = b[Y];
+
 #define PUTBGR24(l, i, abase)                       \
     Y                  = py_##l[2 * i];             \
     dst_##l[6 * i + 0] = b[Y];                      \
@@ -98,12 +108,22 @@ const int *sws_getCoefficients(int colorspace)
     dst_##l[6 * i + 4] = g[Y];                      \
     dst_##l[6 * i + 5] = r[Y];
 
+#define PUTBGR24_ODD(l, i, abase)                   \
+    Y                  = py_##l[2 * i];             \
+    dst_##l[6 * i + 0] = b[Y];                      \
+    dst_##l[6 * i + 1] = g[Y];                      \
+    dst_##l[6 * i + 2] = r[Y];
+
 #define PUTRGBA(l, i, abase)                        \
     Y                  = py_##l[2 * i];             \
     dst_##l[2 * i]     = r[Y] + g[Y] + b[Y] + ((uint32_t)(pa_##l[2 * i])     
<< abase); \
     Y                  = py_##l[2 * i + 1];         \
     dst_##l[2 * i + 1] = r[Y] + g[Y] + b[Y] + ((uint32_t)(pa_##l[2 * i + 1]) 
<< abase);
 
+#define PUTRGBA_ODD(l, i, abase)                    \
+    Y                  = py_##l[2 * i];             \
+    dst_##l[2 * i]     = r[Y] + g[Y] + b[Y] + ((uint32_t)(pa_##l[2 * i])     
<< abase);
+
 #define PUTRGB48(l, i, abase)                       \
     Y                    = py_##l[ 2 * i];          \
     dst_##l[12 * i +  0] = dst_##l[12 * i +  1] = r[Y]; \
@@ -114,6 +134,12 @@ const int *sws_getCoefficients(int colorspace)
     dst_##l[12 * i +  8] = dst_##l[12 * i +  9] = g[Y]; \
     dst_##l[12 * i + 10] = dst_##l[12 * i + 11] = b[Y];
 
+#define PUTRGB48_ODD(l, i, abase)                   \
+    Y                    = py_##l[ 2 * i];          \
+    dst_##l[12 * i +  0] = dst_##l[12 * i +  1] = r[Y]; \
+    dst_##l[12 * i +  2] = dst_##l[12 * i +  3] = g[Y]; \
+    dst_##l[12 * i +  4] = dst_##l[12 * i +  5] = b[Y];
+
 #define PUTBGR48(l, i, abase)                       \
     Y                    = py_##l[2 * i];           \
     dst_##l[12 * i +  0] = dst_##l[12 * i +  1] = b[Y]; \
@@ -124,6 +150,12 @@ const int *sws_getCoefficients(int colorspace)
     dst_##l[12 * i +  8] = dst_##l[12 * i +  9] = g[Y]; \
     dst_##l[12 * i + 10] = dst_##l[12 * i + 11] = r[Y];
 
+#define PUTBGR48_ODD(l, i, abase)                   \
+    Y                    = py_##l[2 * i];           \
+    dst_##l[12 * i +  0] = dst_##l[12 * i +  1] = b[Y]; \
+    dst_##l[12 * i +  2] = dst_##l[12 * i +  3] = g[Y]; \
+    dst_##l[12 * i +  4] = dst_##l[12 * i +  5] = r[Y];
+
 #define PUTGBRP(l, i, abase)                        \
     Y                   = py_##l[2 * i];            \
     dst_##l [2 * i + 0] = g[Y];                     \
@@ -134,6 +166,12 @@ const int *sws_getCoefficients(int colorspace)
     dst1_##l[2 * i + 1] = b[Y];                     \
     dst2_##l[2 * i + 1] = r[Y];
 
+#define PUTGBRP_ODD(l, i, abase)                    \
+    Y                   = py_##l[2 * i];            \
+    dst_##l [2 * i + 0] = g[Y];                     \
+    dst1_##l[2 * i + 0] = b[Y];                     \
+    dst2_##l[2 * i + 0] = r[Y];
+
 #define YUV2RGBFUNC(func_name, dst_type, alpha, yuv422, nb_dst_planes)      \
     static int func_name(SwsInternal *c, const uint8_t *const src[],         \
                          const int srcStride[], int srcSliceY, int srcSliceH, \
@@ -233,6 +271,10 @@ const int *sws_getCoefficients(int colorspace)
         LOADCHROMA(1, 0);                                               \
         PUTFUNC(1, 0, abase);                                           \
         PUTFUNC(2, 0, abase);                                           \
+    ENDYUV2RGBLINE(dst_delta, 2, alpha, 0, nb_dst_planes)               \
+        LOADCHROMA(1, 0);                                               \
+        PUTFUNC##_ODD(1, 0, abase);                                     \
+        PUTFUNC##_ODD(2, 0, abase);                                     \
     ENDYUV2RGBFUNC()
 
 #define YUV422FUNC(func_name, dst_type, alpha, abase, PUTFUNC, dst_delta, 
nb_dst_planes) \
@@ -278,6 +320,12 @@ const int *sws_getCoefficients(int colorspace)
                                                                         \
         LOADCHROMA(2, 0);                                               \
         PUTFUNC(2, 0, abase);                                           \
+    ENDYUV2RGBLINE(dst_delta, 2, alpha, 1, nb_dst_planes)               \
+        LOADCHROMA(1, 0);                                               \
+        PUTFUNC##_ODD(1, 0, abase);                                     \
+                                                                        \
+        LOADCHROMA(2, 0);                                               \
+        PUTFUNC##_ODD(2, 0, abase);                                     \
     ENDYUV2RGBFUNC()
 
 #define YUV420FUNC_DITHER(func_name, dst_type, LOADDITHER, PUTFUNC, dst_delta) 
\
@@ -315,6 +363,12 @@ const int *sws_getCoefficients(int colorspace)
         LOADCHROMA(1, 0);                                               \
         PUTFUNC(1, 0, 0);                                               \
         PUTFUNC(2, 0, 0 + 8);                                           \
+    ENDYUV2RGBLINE(dst_delta, 2, 0, 0, 1)                               \
+        LOADDITHER                                                      \
+                                                                        \
+        LOADCHROMA(1, 0);                                               \
+        PUTFUNC##_ODD(1, 0, 0);                                         \
+        PUTFUNC##_ODD(2, 0, 0 + 8);                                     \
     ENDYUV2RGBFUNC()
 
 #define YUV422FUNC_DITHER(func_name, dst_type, LOADDITHER, PUTFUNC, dst_delta) 
\
@@ -366,6 +420,14 @@ const int *sws_getCoefficients(int colorspace)
                                                                         \
         LOADCHROMA(2, 0);                                               \
         PUTFUNC(2, 0, 0 + 8);                                           \
+    ENDYUV2RGBLINE(dst_delta, 2, 0, 1, 1)                               \
+        LOADDITHER                                                      \
+                                                                        \
+        LOADCHROMA(1, 0);                                               \
+        PUTFUNC##_ODD(1, 0, 0);                                         \
+                                                                        \
+        LOADCHROMA(2, 0);                                               \
+        PUTFUNC##_ODD(2, 0, 0 + 8);                                     \
     ENDYUV2RGBFUNC()
 
 #define LOADDITHER16                                    \
@@ -383,6 +445,12 @@ const int *sws_getCoefficients(int colorspace)
                          g[Y + e16[1 + o]] +        \
                          b[Y + f16[1 + o]];
 
+#define PUTRGB16_ODD(l, i, o)                       \
+    Y                  = py_##l[2 * i];             \
+    dst_##l[2 * i]     = r[Y + d16[0 + o]] +        \
+                         g[Y + e16[0 + o]] +        \
+                         b[Y + f16[0 + o]];
+
 #define LOADDITHER15                                    \
     const uint8_t *d16 = ff_dither_2x2_8[y & 1];        \
     const uint8_t *e16 = ff_dither_2x2_8[(y & 1)^1];
@@ -397,6 +465,12 @@ const int *sws_getCoefficients(int colorspace)
                          g[Y + d16[0 + o]] +        \
                          b[Y + e16[1 + o]];
 
+#define PUTRGB15_ODD(l, i, o)                       \
+    Y                  = py_##l[2 * i];             \
+    dst_##l[2 * i]     = r[Y + d16[0 + o]] +        \
+                         g[Y + d16[1 + o]] +        \
+                         b[Y + e16[0 + o]];
+
 #define LOADDITHER12                                    \
     const uint8_t *d16 = ff_dither_4x4_16[y & 3];
 
@@ -410,6 +484,12 @@ const int *sws_getCoefficients(int colorspace)
                          g[Y + d16[1 + o]] +        \
                          b[Y + d16[1 + o]];
 
+#define PUTRGB12_ODD(l, i, o)                       \
+    Y                  = py_##l[2 * i];             \
+    dst_##l[2 * i]     = r[Y + d16[0 + o]] +        \
+                         g[Y + d16[0 + o]] +        \
+                         b[Y + d16[0 + o]];
+
 #define LOADDITHER8                                     \
     const uint8_t *d32 = ff_dither_8x8_32[yd & 7];      \
     const uint8_t *d64 = ff_dither_8x8_73[yd & 7];
@@ -424,6 +504,12 @@ const int *sws_getCoefficients(int colorspace)
                          g[Y + d32[1 + o]] +        \
                          b[Y + d64[1 + o]];
 
+#define PUTRGB8_ODD(l, i, o)                        \
+    Y                  = py_##l[2 * i];             \
+    dst_##l[2 * i]     = r[Y + d32[0 + o]] +        \
+                         g[Y + d32[0 + o]] +        \
+                         b[Y + d64[0 + o]];
+
 #define LOADDITHER4D                                    \
     const uint8_t * d64 = ff_dither_8x8_73[yd & 7];     \
     const uint8_t *d128 = ff_dither_8x8_220[yd & 7];    \
@@ -440,6 +526,13 @@ const int *sws_getCoefficients(int colorspace)
               b[Y + d128[1 + o]]) << 4;             \
     dst_##l[i] = acc;
 
+#define PUTRGB4D_ODD(l, i, o)                       \
+    Y      = py_##l[2 * i];                         \
+    acc    = r[Y + d128[0 + o]] +                   \
+             g[Y +  d64[0 + o]] +                   \
+             b[Y + d128[0 + o]];                    \
+    dst_##l[i] = acc;
+
 #define LOADDITHER4DB                                   \
     const uint8_t *d64  = ff_dither_8x8_73[yd & 7];     \
     const uint8_t *d128 = ff_dither_8x8_220[yd & 7];
@@ -454,6 +547,12 @@ const int *sws_getCoefficients(int colorspace)
                          g[Y +  d64[1 + o]] +       \
                          b[Y + d128[1 + o]];
 
+#define PUTRGB4DB_ODD(l, i, o)                      \
+    Y                  = py_##l[2 * i];             \
+    dst_##l[2 * i]     = r[Y + d128[0 + o]] +       \
+                         g[Y +  d64[0 + o]] +       \
+                         b[Y + d128[0 + o]];
+
 YUV2RGBFUNC(yuv2rgb_c_1_ordered_dither, uint8_t, 0, 0, 1)
     const uint8_t *d128 = ff_dither_8x8_220[yd & 7];
     char out_1 = 0, out_2 = 0;
-- 
2.54.0

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

Reply via email to