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]
