Commit 72f8c3376b for ffmpeg
commit 72f8c3376be3fe77f17a3c3b27eff1326895250f
Author: Dom Cobley <popcornmix@gmail.com>
Date: Tue Nov 3 16:53:22 2026 +0000
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 when the destination stride has
room for that.
Add a single pixel stage after the 2 pixel stage. The dithered formats
use the dither matrix column of the final pixel, and packed RGB4/BGR4
put it in the high nibble, which is the first pixel of the byte.
Add FATE tests at an odd width for output formats which have no SIMD
version.
Signed-off-by: Dom Cobley <popcornmix@gmail.com>
diff --git a/libswscale/yuv2rgb.c b/libswscale/yuv2rgb.c
index bb15ab1f77..37ff1b140e 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, (c->opts.dst_w & 6)); \
+ PUTFUNC##_ODD(2, 0, ((c->opts.dst_w & 6) + 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, (c->opts.dst_w & 6)); \
+ \
+ LOADCHROMA(2, 0); \
+ PUTFUNC##_ODD(2, 0, ((c->opts.dst_w & 6) + 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 << 4;
+
#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;
diff --git a/tests/fate/libswscale.mak b/tests/fate/libswscale.mak
index f0428de00c..7e6a6712cb 100644
--- a/tests/fate/libswscale.mak
+++ b/tests/fate/libswscale.mak
@@ -54,6 +54,18 @@ fate-sws-monow-ed-tail: CMD = framecrc -lavfi color=black:s=100x8:r=1,scale=flag
fate-sws-monob-ed-tail-bilinear: CMD = framecrc -lavfi color=white:s=102x8:r=1,scale=102:16:flags=bilinear+bitexact:sws_dither=ed,format=monob -frames:v 1
fate-sws-monow-ed-tail-bilinear: CMD = framecrc -lavfi color=black:s=102x8:r=1,scale=102:16:flags=bilinear+bitexact:sws_dither=ed,format=monow -frames:v 1
+# Odd width tails of the unscaled yuv2rgb C converters. These output formats
+# have no SIMD version, so the output is the same on all architectures.
+FATE_SWS_YUV2RGB_ODD = fate-sws-yuv2rgb-odd-yuv420p-rgb4 \
+ fate-sws-yuv2rgb-odd-yuv422p-rgb4 \
+ fate-sws-yuv2rgb-odd-yuv420p-bgr48le \
+ fate-sws-yuv2rgb-odd-yuv422p-rgb48le
+FATE_LIBSWSCALE_FFMPEG-$(call FILTERFRAMECRC, TESTSRC2 CROP SCALE FORMAT) += $(FATE_SWS_YUV2RGB_ODD)
+$(FATE_SWS_YUV2RGB_ODD): SRC_FMT = $(word 5,$(subst -, ,$(@)))
+$(FATE_SWS_YUV2RGB_ODD): DST_FMT = $(word 6,$(subst -, ,$(@)))
+$(FATE_SWS_YUV2RGB_ODD): CMD = framecrc -lavfi testsrc2=s=40x8:r=1,format=$(SRC_FMT),crop=39:8:0:0:exact=1,scale,format=$(DST_FMT) -frames:v 1
+fate-sws-yuv2rgb-odd: $(FATE_SWS_YUV2RGB_ODD)
+
SWS_UNSTABLE_BACKENDS-yes += c memcpy
ifeq ($(ARCH_X86_64),yes)
SWS_UNSTABLE_BACKENDS-$(HAVE_X86ASM) += x86
diff --git a/tests/ref/fate/sws-yuv2rgb-odd-yuv420p-bgr48le b/tests/ref/fate/sws-yuv2rgb-odd-yuv420p-bgr48le
new file mode 100644
index 0000000000..92949e2c98
--- /dev/null
+++ b/tests/ref/fate/sws-yuv2rgb-odd-yuv420p-bgr48le
@@ -0,0 +1,6 @@
+#tb 0: 1/1
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 39x8
+#sar 0: 1/1
+0, 0, 0, 1, 1872, 0xff4fd0e1
diff --git a/tests/ref/fate/sws-yuv2rgb-odd-yuv420p-rgb4 b/tests/ref/fate/sws-yuv2rgb-odd-yuv420p-rgb4
new file mode 100644
index 0000000000..94d68ea53c
--- /dev/null
+++ b/tests/ref/fate/sws-yuv2rgb-odd-yuv420p-rgb4
@@ -0,0 +1,6 @@
+#tb 0: 1/1
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 39x8
+#sar 0: 1/1
+0, 0, 0, 1, 160, 0x2ada3326
diff --git a/tests/ref/fate/sws-yuv2rgb-odd-yuv422p-rgb4 b/tests/ref/fate/sws-yuv2rgb-odd-yuv422p-rgb4
new file mode 100644
index 0000000000..06822ba5c7
--- /dev/null
+++ b/tests/ref/fate/sws-yuv2rgb-odd-yuv422p-rgb4
@@ -0,0 +1,6 @@
+#tb 0: 1/1
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 39x8
+#sar 0: 1/1
+0, 0, 0, 1, 160, 0x2b8232c0
diff --git a/tests/ref/fate/sws-yuv2rgb-odd-yuv422p-rgb48le b/tests/ref/fate/sws-yuv2rgb-odd-yuv422p-rgb48le
new file mode 100644
index 0000000000..b967eecee6
--- /dev/null
+++ b/tests/ref/fate/sws-yuv2rgb-odd-yuv422p-rgb48le
@@ -0,0 +1,6 @@
+#tb 0: 1/1
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 39x8
+#sar 0: 1/1
+0, 0, 0, 1, 1872, 0x5ffecf3d