PR #25566 opened by Kacper Michajłow (kasper93)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25566
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25566.patch


From c66db00e01bdd61a30f7d467c83df89ce34539ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sat, 24 Oct 2026 04:06:38 +0200
Subject: [PATCH 01/13] avfilter/colorspace: derive the RGB to XYZ matrix
 without dividing by y
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The matrix columns were the primaries scaled to Y = 1, x/y and
(1 - x - y)/y, and were then rescaled to fit the white point, so the
division by y cancels out. Use the xyz chromaticity vectors directly,
which gives the same matrix and also works for a primary with y = 0,
such as the Z axis of the CIE 1931 XYZ.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libavfilter/colorspace.c | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/libavfilter/colorspace.c b/libavfilter/colorspace.c
index f0bd14be18a..2655d96363d 100644
--- a/libavfilter/colorspace.c
+++ b/libavfilter/colorspace.c
@@ -86,13 +86,15 @@ void ff_fill_rgb2xyz_table(const AVPrimaryCoefficients 
*coeffs,
     double xb = av_q2d(coeffs->b.x), yb = av_q2d(coeffs->b.y);
     double xw = av_q2d(wp->x), yw = av_q2d(wp->y);
 
-    rgb2xyz[0][0] = xr / yr;
-    rgb2xyz[0][1] = xg / yg;
-    rgb2xyz[0][2] = xb / yb;
-    rgb2xyz[1][0] = rgb2xyz[1][1] = rgb2xyz[1][2] = 1.0;
-    rgb2xyz[2][0] = (1.0 - xr - yr) / yr;
-    rgb2xyz[2][1] = (1.0 - xg - yg) / yg;
-    rgb2xyz[2][2] = (1.0 - xb - yb) / yb;
+    rgb2xyz[0][0] = xr;
+    rgb2xyz[0][1] = xg;
+    rgb2xyz[0][2] = xb;
+    rgb2xyz[1][0] = yr;
+    rgb2xyz[1][1] = yg;
+    rgb2xyz[1][2] = yb;
+    rgb2xyz[2][0] = 1.0 - xr - yr;
+    rgb2xyz[2][1] = 1.0 - xg - yg;
+    rgb2xyz[2][2] = 1.0 - xb - yb;
     ff_matrix_invert_3x3(rgb2xyz, i);
     zw = 1.0 - xw - yw;
     sr = i[0][0] * xw + i[0][1] * yw + i[0][2] * zw;
-- 
2.54.0


From 5789ddf0ff58944aaf772368490338cce8d782ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sat, 24 Oct 2026 04:06:39 +0200
Subject: [PATCH 02/13] swscale/csputils: derive the RGB to XYZ matrix without
 dividing by y
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

ff_sws_rgb2xyz built its columns from X/Y and Z/Y of each primary and
then solved for the scale that fits the white point, so the division by
y cancels out, while av_div_q by y = 0 turned the matrix into infinities
and NaNs. Use the xyz chromaticity vectors directly, which gives the
same matrix and also works for a primary with y = 0, such as the Z axis
of the CIE 1931 XYZ.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libswscale/csputils.c | 25 ++++++++++++++-----------
 1 file changed, 14 insertions(+), 11 deletions(-)

diff --git a/libswscale/csputils.c b/libswscale/csputils.c
index 6ab6ac3f7a8..1c2c96da277 100644
--- a/libswscale/csputils.c
+++ b/libswscale/csputils.c
@@ -92,23 +92,26 @@ static inline AVRational cie_Z(AVCIExy xy)
 SwsMatrix3x3 ff_sws_rgb2xyz(const AVColorPrimariesDesc *desc)
 {
     SwsMatrix3x3 out = {{{0}}};
-    float S[3], X[3], Z[3], Xw, Zw;
+    float S[3], X[3], Y[3], Z[3], Xw, Zw;
 
-    X[0] = av_q2d(cie_X(desc->prim.r));
-    X[1] = av_q2d(cie_X(desc->prim.g));
-    X[2] = av_q2d(cie_X(desc->prim.b));
+    X[0] = av_q2d(desc->prim.r.x);
+    X[1] = av_q2d(desc->prim.g.x);
+    X[2] = av_q2d(desc->prim.b.x);
 
-    Z[0] = av_q2d(cie_Z(desc->prim.r));
-    Z[1] = av_q2d(cie_Z(desc->prim.g));
-    Z[2] = av_q2d(cie_Z(desc->prim.b));
+    Y[0] = av_q2d(desc->prim.r.y);
+    Y[1] = av_q2d(desc->prim.g.y);
+    Y[2] = av_q2d(desc->prim.b.y);
+
+    for (int i = 0; i < 3; i++)
+        Z[i] = 1.0f - X[i] - Y[i];
 
     Xw = av_q2d(cie_X(desc->wp));
     Zw = av_q2d(cie_Z(desc->wp));
 
-    /* S = XYZ^-1 * W */
+    /* S = xyz^-1 * W */
     for (int i = 0; i < 3; i++) {
         out.m[0][i] = X[i];
-        out.m[1][i] = 1.0f;
+        out.m[1][i] = Y[i];
         out.m[2][i] = Z[i];
     }
 
@@ -117,10 +120,10 @@ SwsMatrix3x3 ff_sws_rgb2xyz(const AVColorPrimariesDesc 
*desc)
     for (int i = 0; i < 3; i++)
         S[i] = out.m[i][0] * Xw + out.m[i][1] + out.m[i][2] * Zw;
 
-    /* M = [Sc * XYZc] */
+    /* M = [Sc * xyzc] */
     for (int i = 0; i < 3; i++) {
         out.m[0][i] = S[i] * X[i];
-        out.m[1][i] = S[i];
+        out.m[1][i] = S[i] * Y[i];
         out.m[2][i] = S[i] * Z[i];
     }
 
-- 
2.54.0


From b517b5e42496e0e6edfd0e606628623d67aa1233 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sat, 24 Oct 2026 21:02:20 +0200
Subject: [PATCH 03/13] avfilter/vf_colorspace: refuse a primaries conversion
 that does not fit the fixed point
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The linear RGB to RGB matrix is stored as int16 with 14 fractional
bits, so a coefficient of 2 or more wraps around. The conversion from
the XYZ basis of SMPTE ST 428-1 to BT.709 has one of 3.15, and a
neutral (126, 128, 128) came out as (99, 143, 64). Check the
coefficients before narrowing them and refuse the conversion. Fast mode
does not use the matrix and is not checked, and a refused conversion
forgets the primaries, so that the next frame is refused too instead of
using the matrix left behind.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libavfilter/vf_colorspace.c | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/libavfilter/vf_colorspace.c b/libavfilter/vf_colorspace.c
index 5e788680646..5f0523cae2e 100644
--- a/libavfilter/vf_colorspace.c
+++ b/libavfilter/vf_colorspace.c
@@ -484,7 +484,7 @@ static int create_filtergraph(AVFilterContext *ctx,
         }
         s->lrgb2lrgb_passthrough = !memcmp(s->in_primaries, s->out_primaries,
                                            sizeof(*s->in_primaries));
-        if (!s->lrgb2lrgb_passthrough) {
+        if (!s->lrgb2lrgb_passthrough && !s->fast_mode) {
             double rgb2xyz[3][3], xyz2rgb[3][3], rgb2rgb[3][3];
             const AVWhitepointCoefficients *wp_out, *wp_in;
 
@@ -506,7 +506,18 @@ static int create_filtergraph(AVFilterContext *ctx,
             }
             for (m = 0; m < 3; m++)
                 for (n = 0; n < 3; n++) {
-                    s->lrgb2lrgb_coeffs[m][n][0] = lrint(16384.0 * 
rgb2rgb[m][n]);
+                    long coeff = lrint(16384.0 * rgb2rgb[m][n]);
+                    if (coeff < INT16_MIN || coeff > INT16_MAX) {
+                        av_log(ctx, AV_LOG_ERROR,
+                               "Conversion from %s to %s primaries does not 
fit "
+                               "the fixed point range of the filter\n",
+                               av_color_primaries_name(s->in_prm),
+                               av_color_primaries_name(s->out_prm));
+                        s->in_primaries  = NULL;
+                        s->out_primaries = NULL;
+                        return AVERROR(EINVAL);
+                    }
+                    s->lrgb2lrgb_coeffs[m][n][0] = coeff;
                     for (o = 1; o < 8; o++)
                         s->lrgb2lrgb_coeffs[m][n][o] = 
s->lrgb2lrgb_coeffs[m][n][0];
                 }
-- 
2.54.0


From b900cfa18a1a5693f78c5acbd2777cae58706b44 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sun, 25 Oct 2026 07:33:16 +0100
Subject: [PATCH 04/13] avcodec/libjxlenc: fail when the color encoding cannot
 be set
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libavcodec/libjxlenc.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/libavcodec/libjxlenc.c b/libavcodec/libjxlenc.c
index c8c864d2661..0852ece70e0 100644
--- a/libavcodec/libjxlenc.c
+++ b/libavcodec/libjxlenc.c
@@ -510,8 +510,11 @@ static int libjxl_preprocess_stream(AVCodecContext *avctx, 
const AVFrame *frame,
     }
 
     /* jret != JXL_ENC_SUCCESS means fallthrough from above */
-    if (!sd || !sd->size || jret != JXL_ENC_SUCCESS)
-        libjxl_populate_colorspace(avctx, frame, pix_desc, &info);
+    if (!sd || !sd->size || jret != JXL_ENC_SUCCESS) {
+        ret = libjxl_populate_colorspace(avctx, frame, pix_desc, &info);
+        if (ret < 0)
+            goto end;
+    }
 
 #if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 8, 0)
     jret = JxlEncoderSetFrameBitDepth(ctx->options, &jxl_bit_depth);
-- 
2.54.0


From 5670c18b3123f355d70efe006be86022620924ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sat, 24 Oct 2026 20:53:19 +0200
Subject: [PATCH 05/13] avcodec/libjxlenc: do not pass SMPTE ST 428-1 XYZ as
 custom primaries
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

libjxl rejects primaries with zero coordinates, which the XYZ basis of
H.273 value 10 has, so JxlEncoderSetColorEncoding fails. A JXL color
encoding describes an RGB space, XYZ needs an ICC profile.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libavcodec/libjxlenc.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/libavcodec/libjxlenc.c b/libavcodec/libjxlenc.c
index 0852ece70e0..e5b09c0815e 100644
--- a/libavcodec/libjxlenc.c
+++ b/libavcodec/libjxlenc.c
@@ -229,6 +229,10 @@ static int libjxl_populate_primaries(void *avctx, 
JxlColorEncoding *jxl_color, e
         jxl_color->primaries = JXL_PRIMARIES_SRGB;
         jxl_color->white_point = JXL_WHITE_POINT_D65;
         return 0;
+    case AVCOL_PRI_SMPTE428:
+        av_log(avctx, AV_LOG_ERROR, "SMPTE ST 428-1 XYZ cannot be described by 
"
+                                    "a JXL color encoding, use an ICC profile 
instead.\n");
+        return AVERROR(EINVAL);
     }
 
     desc = av_csp_primaries_desc_from_id(prm);
-- 
2.54.0


From 0949dd3e5b5448b5c146223be7fbdf80f9025c6c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sat, 24 Oct 2026 05:05:34 +0200
Subject: [PATCH 06/13] avutil/csp: describe SMPTE ST 428-1 primaries as CIE
 1931 XYZ
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

H.273 value 10, SMPTE ST 428-1 (CIE 1931 XYZ), lists red (X) at (1, 0),
green (Y) at (0, 1), blue (Z) at (0, 0) and the centre white at
(1/3, 1/3).

The table had the monochromatic primaries of the CIE 1931 RGB space
instead.

Note that ST 428-1 only defines the encoding of the XYZ tristimulus
values and the transfer function. XYZ is absolute, so nothing in it
names the white the content was mastered to, and real DCDM content does
not have an equal-energy white. The standard reserves its headroom for
white points such as D55, D61 and D65, and the reference projector of RP
431-2 has DCI white. However, H.273 describes both the primaries (10)
(they shouldn't have) and the transfer (17) of such content, so follow
it here. For decoding, the mastering primaries can be given in place of
value 10 (e.g. AVCOL_PRI_SMPTE431 for a DCDM) with the ST 428-1 transfer
kept.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libavutil/csp.c    | 2 +-
 tests/ref/fate/csp | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/libavutil/csp.c b/libavutil/csp.c
index 8ff53cff5fa..7ebbcc58560 100644
--- a/libavutil/csp.c
+++ b/libavutil/csp.c
@@ -79,7 +79,7 @@ static const AVColorPrimariesDesc 
color_primaries[AVCOL_PRI_NB] = {
     [AVCOL_PRI_BT470BG]   = { WP_D65, { { AVR(0.640), AVR(0.330) }, { 
AVR(0.290), AVR(0.600) }, { AVR(0.150), AVR(0.060) } } },
     [AVCOL_PRI_SMPTE170M] = { WP_D65, { { AVR(0.630), AVR(0.340) }, { 
AVR(0.310), AVR(0.595) }, { AVR(0.155), AVR(0.070) } } },
     [AVCOL_PRI_SMPTE240M] = { WP_D65, { { AVR(0.630), AVR(0.340) }, { 
AVR(0.310), AVR(0.595) }, { AVR(0.155), AVR(0.070) } } },
-    [AVCOL_PRI_SMPTE428]  = { WP_E,   { { AVR(0.735), AVR(0.265) }, { 
AVR(0.274), AVR(0.718) }, { AVR(0.167), AVR(0.009) } } },
+    [AVCOL_PRI_SMPTE428]  = { WP_E,   { { AVR(1.000), AVR(0.000) }, { 
AVR(0.000), AVR(1.000) }, { AVR(0.000), AVR(0.000) } } },
     [AVCOL_PRI_SMPTE431]  = { WP_DCI, { { AVR(0.680), AVR(0.320) }, { 
AVR(0.265), AVR(0.690) }, { AVR(0.150), AVR(0.060) } } },
     [AVCOL_PRI_SMPTE432]  = { WP_D65, { { AVR(0.680), AVR(0.320) }, { 
AVR(0.265), AVR(0.690) }, { AVR(0.150), AVR(0.060) } } },
     [AVCOL_PRI_FILM]      = { WP_C,   { { AVR(0.681), AVR(0.319) }, { 
AVR(0.243), AVR(0.692) }, { AVR(0.145), AVR(0.049) } } },
diff --git a/tests/ref/fate/csp b/tests/ref/fate/csp
index bc36bc66bac..ff04a5f8492 100644
--- a/tests/ref/fate/csp
+++ b/tests/ref/fate/csp
@@ -67,9 +67,9 @@ prm=bt2020           ->
   round-trip id=bt2020           desc=OK
 prm=smpte428         ->
   wp =(1/3, 1/3)
-  r  =(73500/100000, 26500/100000)
-  g  =(27400/100000, 71800/100000)
-  b  =(16700/100000, 900/100000)
+  r  =(100000/100000, 0/100000)
+  g  =(0/100000, 100000/100000)
+  b  =(0/100000, 0/100000)
   round-trip id=smpte428         desc=OK
 prm=smpte431         ->
   wp =(31400/100000, 35100/100000)
-- 
2.54.0


From df85458fe030c3251da47d16b27c9642ca3490ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sat, 24 Oct 2026 20:12:25 +0200
Subject: [PATCH 07/13] swscale: name the XYZ12 table setup after SMPTE ST
 428-1
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The XYZ12 formats are converted with the 2.6 power of ST 428-1 (DCDM)
whatever a frame is tagged with, the path is chosen by the pixel format
alone and there is no other XYZ in swscale. Say so in the name of the
table setup, to make clear that it's ST 428.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libswscale/swscale_internal.h | 2 +-
 libswscale/utils.c            | 4 ++--
 tests/checkasm/sw_xyz2rgb.c   | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/libswscale/swscale_internal.h b/libswscale/swscale_internal.h
index f3435be1a09..5d641fb02a4 100644
--- a/libswscale/swscale_internal.h
+++ b/libswscale/swscale_internal.h
@@ -736,7 +736,7 @@ av_cold void ff_sws_init_range_convert_x86(SwsInternal *c);
 av_cold void ff_sws_init_xyzdsp(SwsInternal *c);
 av_cold void ff_sws_init_xyzdsp_aarch64(SwsInternal *c);
 
-av_cold int ff_sws_fill_xyztables(SwsInternal *c);
+av_cold int ff_sws_fill_st428_tables(SwsInternal *c);
 
 SwsFunc ff_yuv2rgb_init_x86(SwsInternal *c);
 SwsFunc ff_yuv2rgb_init_ppc(SwsInternal *c);
diff --git a/libswscale/utils.c b/libswscale/utils.c
index e970b9f035c..043c1e9e769 100644
--- a/libswscale/utils.c
+++ b/libswscale/utils.c
@@ -749,7 +749,7 @@ static av_cold void init_xyz_tables(void)
     }
 }
 
-av_cold int ff_sws_fill_xyztables(SwsInternal *c)
+av_cold int ff_sws_fill_st428_tables(SwsInternal *c)
 {
     static const int16_t xyz2rgb_matrix[3][3] = {
         {13270, -6295, -2041},
@@ -853,7 +853,7 @@ static int handle_formats(SwsContext *sws)
     c->srcXYZ    |= handle_xyz(&sws->src_format);
     c->dstXYZ    |= handle_xyz(&sws->dst_format);
     if (c->srcXYZ || c->dstXYZ)
-        return ff_sws_fill_xyztables(c);
+        return ff_sws_fill_st428_tables(c);
     else
         return 0;
 }
diff --git a/tests/checkasm/sw_xyz2rgb.c b/tests/checkasm/sw_xyz2rgb.c
index 17c5fcce7b4..91180ff9a42 100644
--- a/tests/checkasm/sw_xyz2rgb.c
+++ b/tests/checkasm/sw_xyz2rgb.c
@@ -65,7 +65,7 @@ static void check_xyz12Torgb48le(void)
     memset(&c, 0, sizeof(c));
     c.opts.src_format = src_pix_fmt;
     ff_sws_init_xyzdsp(&c);
-    ff_sws_fill_xyztables(&c);
+    ff_sws_fill_st428_tables(&c);
 
     randomize_buffers(src, 3 * MAX_LINE_SIZE * NUM_LINES);
 
-- 
2.54.0


From c0e8b03b9c8d9202a2b20e57233da244de29d4db Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sun, 25 Oct 2026 07:33:17 +0100
Subject: [PATCH 08/13] swscale: compute the X'Y'Z' matrices from the BT.709
 primaries
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The matrices were rounded by hand from constants that do not quite
match the BT.709 chromaticities, the largest coefficient is off by 5
in Q12.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libswscale/utils.c                       | 27 +++++++++++++++---------
 tests/ref/fate/filter-pixdesc-xyz12be    |  2 +-
 tests/ref/fate/filter-pixdesc-xyz12le    |  2 +-
 tests/ref/fate/filter-pixfmts-copy       |  4 ++--
 tests/ref/fate/filter-pixfmts-crop       |  4 ++--
 tests/ref/fate/filter-pixfmts-field      |  4 ++--
 tests/ref/fate/filter-pixfmts-fieldorder |  4 ++--
 tests/ref/fate/filter-pixfmts-hflip      |  4 ++--
 tests/ref/fate/filter-pixfmts-il         |  4 ++--
 tests/ref/fate/filter-pixfmts-null       |  4 ++--
 tests/ref/fate/filter-pixfmts-scale      |  4 ++--
 tests/ref/fate/filter-pixfmts-transpose  |  4 ++--
 tests/ref/fate/filter-pixfmts-vflip      |  4 ++--
 tests/ref/pixfmt/gbrp-xyz12le            |  2 +-
 tests/ref/pixfmt/gbrp10-xyz12le          |  2 +-
 tests/ref/pixfmt/gbrp12-xyz12le          |  2 +-
 tests/ref/pixfmt/nv24-xyz12le            |  2 +-
 tests/ref/pixfmt/p410-xyz12le            |  2 +-
 tests/ref/pixfmt/p412-xyz12le            |  2 +-
 tests/ref/pixfmt/rgb24-xyz12le           |  2 +-
 tests/ref/pixfmt/rgb48-xyz12le           |  2 +-
 tests/ref/pixfmt/xyz12le                 |  2 +-
 tests/ref/pixfmt/yuv444p-xyz12le         |  2 +-
 tests/ref/pixfmt/yuv444p10-xyz12le       |  2 +-
 tests/ref/pixfmt/yuv444p12-xyz12le       |  2 +-
 25 files changed, 51 insertions(+), 44 deletions(-)

diff --git a/libswscale/utils.c b/libswscale/utils.c
index 043c1e9e769..4399d267cfb 100644
--- a/libswscale/utils.c
+++ b/libswscale/utils.c
@@ -46,6 +46,7 @@
 #include "libavutil/x86/cpu.h"
 #include "libavutil/loongarch/cpu.h"
 
+#include "csputils.h"
 #include "rgb2rgb.h"
 #include "swscale.h"
 #include "swscale_internal.h"
@@ -749,22 +750,28 @@ static av_cold void init_xyz_tables(void)
     }
 }
 
+static void fill_xyz_matrix(int16_t mat[3][3], const SwsMatrix3x3 *m)
+{
+    for (int i = 0; i < 3; i++) {
+        for (int j = 0; j < 3; j++) {
+            const double v = m->m[i][j] * 4096.0;
+            av_assert1(fabs(v) <= INT16_MAX);
+            mat[i][j] = lrint(v);
+        }
+    }
+}
+
 av_cold int ff_sws_fill_st428_tables(SwsInternal *c)
 {
-    static const int16_t xyz2rgb_matrix[3][3] = {
-        {13270, -6295, -2041},
-        {-3969,  7682,   170},
-        {  228,  -835,  4329} };
-    static const int16_t rgb2xyz_matrix[3][3] = {
-        {1689, 1464,  739},
-        { 871, 2929,  296},
-        {  79,  488, 3891} };
+    const AVColorPrimariesDesc *bt709 = 
av_csp_primaries_desc_from_id(AVCOL_PRI_BT709);
+    const SwsMatrix3x3 xyz2rgb = ff_sws_xyz2rgb(bt709);
+    const SwsMatrix3x3 rgb2xyz = ff_sws_rgb2xyz(bt709);
 
     if (c->xyz2rgb.gamma.in)
         return 0;
 
-    memcpy(c->xyz2rgb.mat, xyz2rgb_matrix, sizeof(c->xyz2rgb.mat));
-    memcpy(c->rgb2xyz.mat, rgb2xyz_matrix, sizeof(c->rgb2xyz.mat));
+    fill_xyz_matrix(c->xyz2rgb.mat, &xyz2rgb);
+    fill_xyz_matrix(c->rgb2xyz.mat, &rgb2xyz);
 
 #if CONFIG_SMALL
     c->xyz2rgb.gamma.in = av_malloc(sizeof(uint16_t) * 2 * (4096 + 65536));
diff --git a/tests/ref/fate/filter-pixdesc-xyz12be 
b/tests/ref/fate/filter-pixdesc-xyz12be
index 3cef4083c7a..afee7450cc8 100644
--- a/tests/ref/fate/filter-pixdesc-xyz12be
+++ b/tests/ref/fate/filter-pixdesc-xyz12be
@@ -1 +1 @@
-pixdesc-xyz12be     198f43f452bc55f4ca1e0e0171de5c4c
+pixdesc-xyz12be     03b78ced8e69fd61e55040a3926e10a0
diff --git a/tests/ref/fate/filter-pixdesc-xyz12le 
b/tests/ref/fate/filter-pixdesc-xyz12le
index 6193c3193a1..02a372b15d7 100644
--- a/tests/ref/fate/filter-pixdesc-xyz12le
+++ b/tests/ref/fate/filter-pixdesc-xyz12le
@@ -1 +1 @@
-pixdesc-xyz12le     5ca7d9ab5e01cc1bdc906520926721eb
+pixdesc-xyz12le     2e028ed810f75ee2cda02b1f90519284
diff --git a/tests/ref/fate/filter-pixfmts-copy 
b/tests/ref/fate/filter-pixfmts-copy
index 1b6e658fe8b..1c8218b66cc 100644
--- a/tests/ref/fate/filter-pixfmts-copy
+++ b/tests/ref/fate/filter-pixfmts-copy
@@ -117,8 +117,8 @@ xv36be              9f556ee59a672fd8725f0bb36ce3e4b0
 xv36le              e08dcbde02f1c28a3554f372ad1278e2
 xv48be              ce34993b4b4411bba1d852b9b86aa39e
 xv48le              df913a7e61b162aa98303e5393e60c63
-xyz12be             d4562ab725e3ab7e5573dd03218bb03c
-xyz12le             ce17d867c3fea4c42324e40353b72de4
+xyz12be             c4f3cb794f1c15770c2a8690f49e4395
+xyz12le             938885a1a9150b103adab1d3aefad5f6
 y210le              04e9487b6cce38e7531437e946cdd586
 y212le              825768be8fe92708ae80be84855066ed
 y216le              0e99aeddfee304e72d525d72998d9e9b
diff --git a/tests/ref/fate/filter-pixfmts-crop 
b/tests/ref/fate/filter-pixfmts-crop
index aac5fe298bb..1eec02d7c83 100644
--- a/tests/ref/fate/filter-pixfmts-crop
+++ b/tests/ref/fate/filter-pixfmts-crop
@@ -114,8 +114,8 @@ xv36be              23b6f253fcb375e4145cfcb562268c5f
 xv36le              778286003497f92b84d0bd8258d6b85d
 xv48be              c90889b2cf54cc78bd58e8c47d4eb791
 xv48le              2c15c1254449ec5f9135ae61bdf4e1d5
-xyz12be             05d6fc3fbe70403160ff4e1b8a4aa969
-xyz12le             3c7328a2f8497f0b4635b84df381a2b8
+xyz12be             117d5a92f98bba9dcea367d8d731c80a
+xyz12le             baa4f90965b2964b7b1ecd2a8dbcce60
 ya16be              e2b490dc18f6ca3814fe9d68193ba263
 ya16le              33b0e26f3c2e89c651345970474f6703
 ya8                 51a8dd297e35d40b06d3ebe8f4717895
diff --git a/tests/ref/fate/filter-pixfmts-field 
b/tests/ref/fate/filter-pixfmts-field
index 603bc226151..e7022d5bfd8 100644
--- a/tests/ref/fate/filter-pixfmts-field
+++ b/tests/ref/fate/filter-pixfmts-field
@@ -117,8 +117,8 @@ xv36be              bcc7bda2d0a5d43db4464af6a4cb5d65
 xv36le              ba99f258370f2a56993e8760e6b30194
 xv48be              2abcd986a34789ba4310be3969020d0d
 xv48le              f6f2e33f260f48334197538f3331f7bc
-xyz12be             a17930e991ca4ff86de5c2dc3b1e9b51
-xyz12le             b134b258a9593bc2019f0493f079f3a1
+xyz12be             5f472c753927b9b672d9f54a4d50be2f
+xyz12le             1c57f2d6fbc86123674620fe16762b70
 y210le              4c2fba1dc40322584977d15dd07c9146
 y212le              ac2a47c45187dd54d0f55293cbffd954
 y216le              e65b5bfae1b40edbbed2012e9cd45e31
diff --git a/tests/ref/fate/filter-pixfmts-fieldorder 
b/tests/ref/fate/filter-pixfmts-fieldorder
index 2e9ab58d587..bbf9e39543b 100644
--- a/tests/ref/fate/filter-pixfmts-fieldorder
+++ b/tests/ref/fate/filter-pixfmts-fieldorder
@@ -106,8 +106,8 @@ xv36be              962386c88268f4382004c3a7a82c5eb8
 xv36le              bcceffc985aaa8414c4b8072aa0889bd
 xv48be              4d6e4004b03767f12df8bb4e76c98ddf
 xv48le              9e94d82461a2131063157ac0dbe9467b
-xyz12be             65fe8da4ad44c83855360144eba41853
-xyz12le             f13851b3ba4e511ab1436786ec0727bd
+xyz12be             4d1f54d3d5140b23a7ed8d6746e9c08e
+xyz12le             a12154aa9b0f61c6a76382a5515b9234
 y210le              22b1a02a39c4b325726bf8793bf1e8f2
 y212le              2f08fb195b948056c844acb1eee8d649
 y216le              360cb98ac80b13d3a8ec61c9f1ff3bac
diff --git a/tests/ref/fate/filter-pixfmts-hflip 
b/tests/ref/fate/filter-pixfmts-hflip
index 5889c6caa4f..c379b42d6f9 100644
--- a/tests/ref/fate/filter-pixfmts-hflip
+++ b/tests/ref/fate/filter-pixfmts-hflip
@@ -114,8 +114,8 @@ xv36be              98f578df965eed369f46cb135e2d1345
 xv36le              e478b4b54698beb3ce1b9a2dd691d544
 xv48be              e030a2c7b1b600cfacb691b6e90c2e3d
 xv48le              fbd7f8c65cd6fc9f9108dc9a1f977dc3
-xyz12be             c678c7f19c610063f352a0253121441f
-xyz12le             2320702cb1c8ccaad27d7004d4f3a195
+xyz12be             4b778cc2db5060f76db855f7800de41d
+xyz12le             f39689517dbee5988a9cc94f1b0db438
 ya16be              e7665e62e476df4eb143420e57cc9ec2
 ya16le              3a23b71db418b74e1cc304d7cd3e1989
 ya8                 4ad5920716de3d2fbbc49f95adb60345
diff --git a/tests/ref/fate/filter-pixfmts-il b/tests/ref/fate/filter-pixfmts-il
index 03ff9c92245..ce887db6f01 100644
--- a/tests/ref/fate/filter-pixfmts-il
+++ b/tests/ref/fate/filter-pixfmts-il
@@ -116,8 +116,8 @@ xv36be              3bbb949278ea55cc947ee03bd9c27c2d
 xv36le              102c0e817d375ddd6b2cfbb4262dec95
 xv48be              4d7376651fb7b3e84d00abad6c785aad
 xv48le              a1a8ff16d9a864568e5e557734bf3d6d
-xyz12be             3d9ae42c65eb645bb8e99dc708716f2d
-xyz12le             b27ec91dcff02d2b94b17c136be55001
+xyz12be             aec98321799e590cc58aed1202da70a3
+xyz12le             7e54e36d02b73747c47574aa25e83d8a
 y210le              d4cf9b53cd7ff22f087743d483e88480
 y212le              d5a2b4677ddb4a3bc3e5cd5cbb20f426
 y216le              9e44c6d76b09bcbe71738423b4b3d67a
diff --git a/tests/ref/fate/filter-pixfmts-null 
b/tests/ref/fate/filter-pixfmts-null
index 1b6e658fe8b..1c8218b66cc 100644
--- a/tests/ref/fate/filter-pixfmts-null
+++ b/tests/ref/fate/filter-pixfmts-null
@@ -117,8 +117,8 @@ xv36be              9f556ee59a672fd8725f0bb36ce3e4b0
 xv36le              e08dcbde02f1c28a3554f372ad1278e2
 xv48be              ce34993b4b4411bba1d852b9b86aa39e
 xv48le              df913a7e61b162aa98303e5393e60c63
-xyz12be             d4562ab725e3ab7e5573dd03218bb03c
-xyz12le             ce17d867c3fea4c42324e40353b72de4
+xyz12be             c4f3cb794f1c15770c2a8690f49e4395
+xyz12le             938885a1a9150b103adab1d3aefad5f6
 y210le              04e9487b6cce38e7531437e946cdd586
 y212le              825768be8fe92708ae80be84855066ed
 y216le              0e99aeddfee304e72d525d72998d9e9b
diff --git a/tests/ref/fate/filter-pixfmts-scale 
b/tests/ref/fate/filter-pixfmts-scale
index a6520f25ed9..ecc9a8c312b 100644
--- a/tests/ref/fate/filter-pixfmts-scale
+++ b/tests/ref/fate/filter-pixfmts-scale
@@ -117,8 +117,8 @@ xv36be              4d084adca0228d7750d1e2e877e0d79b
 xv36le              de9c74e94dc19c828e1572aa283d8aca
 xv48be              9e58d1a045df100b0dec116e13be5b4e
 xv48le              fd873d53609b2fbdfe99470f515a234c
-xyz12be             94ed06895d7030b97c36ccb9aae3722a
-xyz12le             fcb73d6b6192ab64ca9ff668fe6562f6
+xyz12be             681537982da2c5cfaa99912100d5a81d
+xyz12le             17c835d55db8d8177b018f9d4461960d
 y210le              7c2aef142d88ab343ec01acd45f38466
 y212le              39a3c0c843041ad4501b3107dd91ef17
 y216le              17be2999e97d36b8ed903f07ef428c09
diff --git a/tests/ref/fate/filter-pixfmts-transpose 
b/tests/ref/fate/filter-pixfmts-transpose
index ebb70d15ed8..628c7652d50 100644
--- a/tests/ref/fate/filter-pixfmts-transpose
+++ b/tests/ref/fate/filter-pixfmts-transpose
@@ -104,8 +104,8 @@ xv36be              2261a0e3db5ee607d37f68d19704ae15
 xv36le              9202133de91bf64c76ca27d5cd0c816a
 xv48be              14373b7fe123225689e76fe2ce43fb93
 xv48le              319df9724a067c7b5efa215f9f54d127
-xyz12be             38ce311e8734b535484c2e2ba70626d0
-xyz12le             24a0e779fce6eaed687cb2c416a61eb9
+xyz12be             fc838024ab1eebf3a400e8a4a1f980d9
+xyz12le             33ab866ecb98fe6d028123ea9906e9a6
 ya16be              9bb39f9567d807be0fb8631dcfa8d5f8
 ya16le              b13453818b83c8dc2610975095393ac1
 ya8                 d4b7a62f80681fa44c977ff3a64f4ce4
diff --git a/tests/ref/fate/filter-pixfmts-vflip 
b/tests/ref/fate/filter-pixfmts-vflip
index e86e9d84e50..e79832a6a93 100644
--- a/tests/ref/fate/filter-pixfmts-vflip
+++ b/tests/ref/fate/filter-pixfmts-vflip
@@ -117,8 +117,8 @@ xv36be              c0272372d3e1a59adb3931ee433a5d5b
 xv36le              ffe6ab75ebc09134c3451f8f6ef0d501
 xv48be              bdfc3217ae456b370dbdcf4d52606a3f
 xv48le              53dbebab73a66539c04644ef56dc6bbb
-xyz12be             b9b83d36d58f4ad129894207888b3b3c
-xyz12le             b9764e43bacbbfc410e4b6558486115f
+xyz12be             f0e4f326736c8158aeb89071e15f6688
+xyz12le             c7817e4c8da785850fa3269aec12c971
 y210le              f8847bedd3ae6e1c0cf84a823f275e31
 y212le              c801725ae31e3b8f5be269359d49f191
 y216le              985db498aedf3fb1c547ad07442b7258
diff --git a/tests/ref/pixfmt/gbrp-xyz12le b/tests/ref/pixfmt/gbrp-xyz12le
index 21982ceb0a2..7bffd86f93a 100644
--- a/tests/ref/pixfmt/gbrp-xyz12le
+++ b/tests/ref/pixfmt/gbrp-xyz12le
@@ -1,2 +1,2 @@
-543fe8482e02d14790b2dc48ec0812e8 *tests/data/pixfmt/gbrp-xyz12le.yuv
+8f95608cd59572ae0a5380da8b8518c6 *tests/data/pixfmt/gbrp-xyz12le.yuv
 7603200 tests/data/pixfmt/gbrp-xyz12le.yuv
diff --git a/tests/ref/pixfmt/gbrp10-xyz12le b/tests/ref/pixfmt/gbrp10-xyz12le
index 6e4c267c611..58acebec6ef 100644
--- a/tests/ref/pixfmt/gbrp10-xyz12le
+++ b/tests/ref/pixfmt/gbrp10-xyz12le
@@ -1,2 +1,2 @@
-68d9a907c0b823c6af7b8cc253b2d148 *tests/data/pixfmt/gbrp10-xyz12le.yuv
+87e0d57bae18f4c69cf0594253a5e97c *tests/data/pixfmt/gbrp10-xyz12le.yuv
 15206400 tests/data/pixfmt/gbrp10-xyz12le.yuv
diff --git a/tests/ref/pixfmt/gbrp12-xyz12le b/tests/ref/pixfmt/gbrp12-xyz12le
index 6c0a78899ad..7002d337712 100644
--- a/tests/ref/pixfmt/gbrp12-xyz12le
+++ b/tests/ref/pixfmt/gbrp12-xyz12le
@@ -1,2 +1,2 @@
-6aeb56c9f41206c8f4a2439b9b755e81 *tests/data/pixfmt/gbrp12-xyz12le.yuv
+882edf8efc53ffa9eb63e87a5acb8194 *tests/data/pixfmt/gbrp12-xyz12le.yuv
 15206400 tests/data/pixfmt/gbrp12-xyz12le.yuv
diff --git a/tests/ref/pixfmt/nv24-xyz12le b/tests/ref/pixfmt/nv24-xyz12le
index 417ad5b0881..8aada49347d 100644
--- a/tests/ref/pixfmt/nv24-xyz12le
+++ b/tests/ref/pixfmt/nv24-xyz12le
@@ -1,2 +1,2 @@
-7a67774f90271fef880b9aa257bb0e31 *tests/data/pixfmt/nv24-xyz12le.yuv
+890bbcd28ea3a06c315acb8c474215a0 *tests/data/pixfmt/nv24-xyz12le.yuv
 7603200 tests/data/pixfmt/nv24-xyz12le.yuv
diff --git a/tests/ref/pixfmt/p410-xyz12le b/tests/ref/pixfmt/p410-xyz12le
index 6b78a5b89e5..2fc2e6864aa 100644
--- a/tests/ref/pixfmt/p410-xyz12le
+++ b/tests/ref/pixfmt/p410-xyz12le
@@ -1,2 +1,2 @@
-2ccc2779172b82392a0c3dae2bcd44fb *tests/data/pixfmt/p410-xyz12le.yuv
+ce3777038372083419a25923d9b6029f *tests/data/pixfmt/p410-xyz12le.yuv
 15206400 tests/data/pixfmt/p410-xyz12le.yuv
diff --git a/tests/ref/pixfmt/p412-xyz12le b/tests/ref/pixfmt/p412-xyz12le
index 0fc28eb10ef..0815cbae13a 100644
--- a/tests/ref/pixfmt/p412-xyz12le
+++ b/tests/ref/pixfmt/p412-xyz12le
@@ -1,2 +1,2 @@
-0c9eff4fa311213ca9602685da946c49 *tests/data/pixfmt/p412-xyz12le.yuv
+7f1ac958f69ba454d23d667ecb4bc035 *tests/data/pixfmt/p412-xyz12le.yuv
 15206400 tests/data/pixfmt/p412-xyz12le.yuv
diff --git a/tests/ref/pixfmt/rgb24-xyz12le b/tests/ref/pixfmt/rgb24-xyz12le
index 19c3184a3cc..6bd3308e5a9 100644
--- a/tests/ref/pixfmt/rgb24-xyz12le
+++ b/tests/ref/pixfmt/rgb24-xyz12le
@@ -1,2 +1,2 @@
-be4b641ec6f5b3c534d6b69e3144c8ca *tests/data/pixfmt/rgb24-xyz12le.yuv
+6c138c678af6e0002167d0b088cbc57a *tests/data/pixfmt/rgb24-xyz12le.yuv
 7603200 tests/data/pixfmt/rgb24-xyz12le.yuv
diff --git a/tests/ref/pixfmt/rgb48-xyz12le b/tests/ref/pixfmt/rgb48-xyz12le
index c7de7622edf..ee4b6cc2f0b 100644
--- a/tests/ref/pixfmt/rgb48-xyz12le
+++ b/tests/ref/pixfmt/rgb48-xyz12le
@@ -1,2 +1,2 @@
-380feb3972a3fd9f4a839585adda3f9b *tests/data/pixfmt/rgb48-xyz12le.yuv
+449f3253f683ea30518dcd345524a080 *tests/data/pixfmt/rgb48-xyz12le.yuv
 15206400 tests/data/pixfmt/rgb48-xyz12le.yuv
diff --git a/tests/ref/pixfmt/xyz12le b/tests/ref/pixfmt/xyz12le
index 312bc9ed574..fd11596b959 100644
--- a/tests/ref/pixfmt/xyz12le
+++ b/tests/ref/pixfmt/xyz12le
@@ -1,2 +1,2 @@
-f14b14a12c864408a0115f55eaef9934 *tests/data/pixfmt/xyz12le.yuv
+8c8f1b04b0fe5aab4fc4f0612b4c3c09 *tests/data/pixfmt/xyz12le.yuv
 304128 tests/data/pixfmt/xyz12le.yuv
diff --git a/tests/ref/pixfmt/yuv444p-xyz12le b/tests/ref/pixfmt/yuv444p-xyz12le
index 7288dc10ebf..e3410a669d7 100644
--- a/tests/ref/pixfmt/yuv444p-xyz12le
+++ b/tests/ref/pixfmt/yuv444p-xyz12le
@@ -1,2 +1,2 @@
-54a3bdec56e545c9ae2782e78b010f76 *tests/data/pixfmt/yuv444p-xyz12le.yuv
+35306788a7a59819bff4dade8c89ddff *tests/data/pixfmt/yuv444p-xyz12le.yuv
 7603200 tests/data/pixfmt/yuv444p-xyz12le.yuv
diff --git a/tests/ref/pixfmt/yuv444p10-xyz12le 
b/tests/ref/pixfmt/yuv444p10-xyz12le
index e3693c35604..38c82d754fe 100644
--- a/tests/ref/pixfmt/yuv444p10-xyz12le
+++ b/tests/ref/pixfmt/yuv444p10-xyz12le
@@ -1,2 +1,2 @@
-749b383b2fb0920de0a35010fca42acb *tests/data/pixfmt/yuv444p10-xyz12le.yuv
+b03201a536e152bff73cdbb9266ed948 *tests/data/pixfmt/yuv444p10-xyz12le.yuv
 15206400 tests/data/pixfmt/yuv444p10-xyz12le.yuv
diff --git a/tests/ref/pixfmt/yuv444p12-xyz12le 
b/tests/ref/pixfmt/yuv444p12-xyz12le
index 6fae982e34e..ebc9105afdd 100644
--- a/tests/ref/pixfmt/yuv444p12-xyz12le
+++ b/tests/ref/pixfmt/yuv444p12-xyz12le
@@ -1,2 +1,2 @@
-3e19343dbcf62d1aa817c30643408439 *tests/data/pixfmt/yuv444p12-xyz12le.yuv
+d9072d39396287f67eb38a1fec1abc6e *tests/data/pixfmt/yuv444p12-xyz12le.yuv
 15206400 tests/data/pixfmt/yuv444p12-xyz12le.yuv
-- 
2.54.0


From e5e561de1e7b67409c7f3e2f8d225029744d8d74 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sat, 24 Oct 2026 20:12:25 +0200
Subject: [PATCH 09/13] swscale: scale the X'Y'Z' conversion as SMPTE ST 428-1
 defines it
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The gamma tables put the top code of X'Y'Z' at linear 1.0, so the
48 cd/m2 reference white of a DCDM, code 3960, came out at 0.918, about
8 percent dark. ST 428-1 (H.273 transfer 17) puts 52.37 cd/m2 at the top
code, L = 52.37/48 * V^2.6, so that the white lands on 1.0 and the codes
above it are headroom. Fold the factor into the matrices, since the X and
Z of a white exceed 1.0 and a 16-bit table cannot hold them: the headroom
clips in RGB after the matrix, and RGB white encodes to code 3960.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libswscale/utils.c                       | 11 +++++++----
 tests/ref/fate/filter-pixdesc-xyz12be    |  2 +-
 tests/ref/fate/filter-pixdesc-xyz12le    |  2 +-
 tests/ref/fate/filter-pixfmts-copy       |  4 ++--
 tests/ref/fate/filter-pixfmts-crop       |  4 ++--
 tests/ref/fate/filter-pixfmts-field      |  4 ++--
 tests/ref/fate/filter-pixfmts-fieldorder |  4 ++--
 tests/ref/fate/filter-pixfmts-hflip      |  4 ++--
 tests/ref/fate/filter-pixfmts-il         |  4 ++--
 tests/ref/fate/filter-pixfmts-null       |  4 ++--
 tests/ref/fate/filter-pixfmts-scale      |  4 ++--
 tests/ref/fate/filter-pixfmts-transpose  |  4 ++--
 tests/ref/fate/filter-pixfmts-vflip      |  4 ++--
 tests/ref/pixfmt/gbrp-xyz12le            |  2 +-
 tests/ref/pixfmt/gbrp10-xyz12le          |  2 +-
 tests/ref/pixfmt/gbrp12-xyz12le          |  2 +-
 tests/ref/pixfmt/nv24-xyz12le            |  2 +-
 tests/ref/pixfmt/p410-xyz12le            |  2 +-
 tests/ref/pixfmt/p412-xyz12le            |  2 +-
 tests/ref/pixfmt/rgb24-xyz12le           |  2 +-
 tests/ref/pixfmt/rgb48-xyz12le           |  2 +-
 tests/ref/pixfmt/xyz12le                 |  2 +-
 tests/ref/pixfmt/yuv444p-xyz12le         |  2 +-
 tests/ref/pixfmt/yuv444p10-xyz12le       |  2 +-
 tests/ref/pixfmt/yuv444p12-xyz12le       |  2 +-
 25 files changed, 41 insertions(+), 38 deletions(-)

diff --git a/libswscale/utils.c b/libswscale/utils.c
index 4399d267cfb..39609915632 100644
--- a/libswscale/utils.c
+++ b/libswscale/utils.c
@@ -750,11 +750,11 @@ static av_cold void init_xyz_tables(void)
     }
 }
 
-static void fill_xyz_matrix(int16_t mat[3][3], const SwsMatrix3x3 *m)
+static void fill_xyz_matrix(int16_t mat[3][3], const SwsMatrix3x3 *m, double 
scale)
 {
     for (int i = 0; i < 3; i++) {
         for (int j = 0; j < 3; j++) {
-            const double v = m->m[i][j] * 4096.0;
+            const double v = m->m[i][j] * scale * 4096.0;
             av_assert1(fabs(v) <= INT16_MAX);
             mat[i][j] = lrint(v);
         }
@@ -763,6 +763,9 @@ static void fill_xyz_matrix(int16_t mat[3][3], const 
SwsMatrix3x3 *m)
 
 av_cold int ff_sws_fill_st428_tables(SwsInternal *c)
 {
+    /* X'Y'Z' puts its 48 cd/m2 white at code 3960 and the top code at
+     * 52.37 cd/m2 (ST 428-1), the codes above the white clip in RGB */
+    const double peak = av_csp_trc_func_inv_from_id(AVCOL_TRC_SMPTE428)(1.0);
     const AVColorPrimariesDesc *bt709 = 
av_csp_primaries_desc_from_id(AVCOL_PRI_BT709);
     const SwsMatrix3x3 xyz2rgb = ff_sws_xyz2rgb(bt709);
     const SwsMatrix3x3 rgb2xyz = ff_sws_rgb2xyz(bt709);
@@ -770,8 +773,8 @@ av_cold int ff_sws_fill_st428_tables(SwsInternal *c)
     if (c->xyz2rgb.gamma.in)
         return 0;
 
-    fill_xyz_matrix(c->xyz2rgb.mat, &xyz2rgb);
-    fill_xyz_matrix(c->rgb2xyz.mat, &rgb2xyz);
+    fill_xyz_matrix(c->xyz2rgb.mat, &xyz2rgb, peak);
+    fill_xyz_matrix(c->rgb2xyz.mat, &rgb2xyz, 1.0 / peak);
 
 #if CONFIG_SMALL
     c->xyz2rgb.gamma.in = av_malloc(sizeof(uint16_t) * 2 * (4096 + 65536));
diff --git a/tests/ref/fate/filter-pixdesc-xyz12be 
b/tests/ref/fate/filter-pixdesc-xyz12be
index afee7450cc8..de4aa304e92 100644
--- a/tests/ref/fate/filter-pixdesc-xyz12be
+++ b/tests/ref/fate/filter-pixdesc-xyz12be
@@ -1 +1 @@
-pixdesc-xyz12be     03b78ced8e69fd61e55040a3926e10a0
+pixdesc-xyz12be     1cb3b45b4be372ea5392f89a2cba9b97
diff --git a/tests/ref/fate/filter-pixdesc-xyz12le 
b/tests/ref/fate/filter-pixdesc-xyz12le
index 02a372b15d7..b815f938233 100644
--- a/tests/ref/fate/filter-pixdesc-xyz12le
+++ b/tests/ref/fate/filter-pixdesc-xyz12le
@@ -1 +1 @@
-pixdesc-xyz12le     2e028ed810f75ee2cda02b1f90519284
+pixdesc-xyz12le     46b9b7bdc4732d2370ebe89c56bc60f7
diff --git a/tests/ref/fate/filter-pixfmts-copy 
b/tests/ref/fate/filter-pixfmts-copy
index 1c8218b66cc..39931533521 100644
--- a/tests/ref/fate/filter-pixfmts-copy
+++ b/tests/ref/fate/filter-pixfmts-copy
@@ -117,8 +117,8 @@ xv36be              9f556ee59a672fd8725f0bb36ce3e4b0
 xv36le              e08dcbde02f1c28a3554f372ad1278e2
 xv48be              ce34993b4b4411bba1d852b9b86aa39e
 xv48le              df913a7e61b162aa98303e5393e60c63
-xyz12be             c4f3cb794f1c15770c2a8690f49e4395
-xyz12le             938885a1a9150b103adab1d3aefad5f6
+xyz12be             c10b0f0f886ff1444d27cb6add490b77
+xyz12le             e6739a863ccf9b26bf6991dc370b4f27
 y210le              04e9487b6cce38e7531437e946cdd586
 y212le              825768be8fe92708ae80be84855066ed
 y216le              0e99aeddfee304e72d525d72998d9e9b
diff --git a/tests/ref/fate/filter-pixfmts-crop 
b/tests/ref/fate/filter-pixfmts-crop
index 1eec02d7c83..73750a265a7 100644
--- a/tests/ref/fate/filter-pixfmts-crop
+++ b/tests/ref/fate/filter-pixfmts-crop
@@ -114,8 +114,8 @@ xv36be              23b6f253fcb375e4145cfcb562268c5f
 xv36le              778286003497f92b84d0bd8258d6b85d
 xv48be              c90889b2cf54cc78bd58e8c47d4eb791
 xv48le              2c15c1254449ec5f9135ae61bdf4e1d5
-xyz12be             117d5a92f98bba9dcea367d8d731c80a
-xyz12le             baa4f90965b2964b7b1ecd2a8dbcce60
+xyz12be             1d86d11684be38cacce52f63fc0f20c5
+xyz12le             122fb7eea3575b4edbbd83219bc7af91
 ya16be              e2b490dc18f6ca3814fe9d68193ba263
 ya16le              33b0e26f3c2e89c651345970474f6703
 ya8                 51a8dd297e35d40b06d3ebe8f4717895
diff --git a/tests/ref/fate/filter-pixfmts-field 
b/tests/ref/fate/filter-pixfmts-field
index e7022d5bfd8..6cec41fe393 100644
--- a/tests/ref/fate/filter-pixfmts-field
+++ b/tests/ref/fate/filter-pixfmts-field
@@ -117,8 +117,8 @@ xv36be              bcc7bda2d0a5d43db4464af6a4cb5d65
 xv36le              ba99f258370f2a56993e8760e6b30194
 xv48be              2abcd986a34789ba4310be3969020d0d
 xv48le              f6f2e33f260f48334197538f3331f7bc
-xyz12be             5f472c753927b9b672d9f54a4d50be2f
-xyz12le             1c57f2d6fbc86123674620fe16762b70
+xyz12be             aa529d2ea8ec254a6e54d90d64be0937
+xyz12le             ad85650ceb61a71bb89acca7cd24c24e
 y210le              4c2fba1dc40322584977d15dd07c9146
 y212le              ac2a47c45187dd54d0f55293cbffd954
 y216le              e65b5bfae1b40edbbed2012e9cd45e31
diff --git a/tests/ref/fate/filter-pixfmts-fieldorder 
b/tests/ref/fate/filter-pixfmts-fieldorder
index bbf9e39543b..003a9ac5f7e 100644
--- a/tests/ref/fate/filter-pixfmts-fieldorder
+++ b/tests/ref/fate/filter-pixfmts-fieldorder
@@ -106,8 +106,8 @@ xv36be              962386c88268f4382004c3a7a82c5eb8
 xv36le              bcceffc985aaa8414c4b8072aa0889bd
 xv48be              4d6e4004b03767f12df8bb4e76c98ddf
 xv48le              9e94d82461a2131063157ac0dbe9467b
-xyz12be             4d1f54d3d5140b23a7ed8d6746e9c08e
-xyz12le             a12154aa9b0f61c6a76382a5515b9234
+xyz12be             ec36fb7f51df4859b71e290c6a44b7b7
+xyz12le             413a52726d022bec91222a36f9e683cd
 y210le              22b1a02a39c4b325726bf8793bf1e8f2
 y212le              2f08fb195b948056c844acb1eee8d649
 y216le              360cb98ac80b13d3a8ec61c9f1ff3bac
diff --git a/tests/ref/fate/filter-pixfmts-hflip 
b/tests/ref/fate/filter-pixfmts-hflip
index c379b42d6f9..75e3a566eae 100644
--- a/tests/ref/fate/filter-pixfmts-hflip
+++ b/tests/ref/fate/filter-pixfmts-hflip
@@ -114,8 +114,8 @@ xv36be              98f578df965eed369f46cb135e2d1345
 xv36le              e478b4b54698beb3ce1b9a2dd691d544
 xv48be              e030a2c7b1b600cfacb691b6e90c2e3d
 xv48le              fbd7f8c65cd6fc9f9108dc9a1f977dc3
-xyz12be             4b778cc2db5060f76db855f7800de41d
-xyz12le             f39689517dbee5988a9cc94f1b0db438
+xyz12be             271a65e4bea9175cb9d7e2c0ea0ba14d
+xyz12le             a8273fe17f2a738f3fd7f457de5c1df4
 ya16be              e7665e62e476df4eb143420e57cc9ec2
 ya16le              3a23b71db418b74e1cc304d7cd3e1989
 ya8                 4ad5920716de3d2fbbc49f95adb60345
diff --git a/tests/ref/fate/filter-pixfmts-il b/tests/ref/fate/filter-pixfmts-il
index ce887db6f01..3f76be05b55 100644
--- a/tests/ref/fate/filter-pixfmts-il
+++ b/tests/ref/fate/filter-pixfmts-il
@@ -116,8 +116,8 @@ xv36be              3bbb949278ea55cc947ee03bd9c27c2d
 xv36le              102c0e817d375ddd6b2cfbb4262dec95
 xv48be              4d7376651fb7b3e84d00abad6c785aad
 xv48le              a1a8ff16d9a864568e5e557734bf3d6d
-xyz12be             aec98321799e590cc58aed1202da70a3
-xyz12le             7e54e36d02b73747c47574aa25e83d8a
+xyz12be             fa10b075452f6fe0b5825453d89eeb81
+xyz12le             5c0bd08982a5219266cd1d3026a08c52
 y210le              d4cf9b53cd7ff22f087743d483e88480
 y212le              d5a2b4677ddb4a3bc3e5cd5cbb20f426
 y216le              9e44c6d76b09bcbe71738423b4b3d67a
diff --git a/tests/ref/fate/filter-pixfmts-null 
b/tests/ref/fate/filter-pixfmts-null
index 1c8218b66cc..39931533521 100644
--- a/tests/ref/fate/filter-pixfmts-null
+++ b/tests/ref/fate/filter-pixfmts-null
@@ -117,8 +117,8 @@ xv36be              9f556ee59a672fd8725f0bb36ce3e4b0
 xv36le              e08dcbde02f1c28a3554f372ad1278e2
 xv48be              ce34993b4b4411bba1d852b9b86aa39e
 xv48le              df913a7e61b162aa98303e5393e60c63
-xyz12be             c4f3cb794f1c15770c2a8690f49e4395
-xyz12le             938885a1a9150b103adab1d3aefad5f6
+xyz12be             c10b0f0f886ff1444d27cb6add490b77
+xyz12le             e6739a863ccf9b26bf6991dc370b4f27
 y210le              04e9487b6cce38e7531437e946cdd586
 y212le              825768be8fe92708ae80be84855066ed
 y216le              0e99aeddfee304e72d525d72998d9e9b
diff --git a/tests/ref/fate/filter-pixfmts-scale 
b/tests/ref/fate/filter-pixfmts-scale
index ecc9a8c312b..3a7b34ad963 100644
--- a/tests/ref/fate/filter-pixfmts-scale
+++ b/tests/ref/fate/filter-pixfmts-scale
@@ -117,8 +117,8 @@ xv36be              4d084adca0228d7750d1e2e877e0d79b
 xv36le              de9c74e94dc19c828e1572aa283d8aca
 xv48be              9e58d1a045df100b0dec116e13be5b4e
 xv48le              fd873d53609b2fbdfe99470f515a234c
-xyz12be             681537982da2c5cfaa99912100d5a81d
-xyz12le             17c835d55db8d8177b018f9d4461960d
+xyz12be             0866f8e5493ea93f4523a9e873a14b1e
+xyz12le             d6990efe1c7141227054cbcda28ea6fb
 y210le              7c2aef142d88ab343ec01acd45f38466
 y212le              39a3c0c843041ad4501b3107dd91ef17
 y216le              17be2999e97d36b8ed903f07ef428c09
diff --git a/tests/ref/fate/filter-pixfmts-transpose 
b/tests/ref/fate/filter-pixfmts-transpose
index 628c7652d50..300e616e042 100644
--- a/tests/ref/fate/filter-pixfmts-transpose
+++ b/tests/ref/fate/filter-pixfmts-transpose
@@ -104,8 +104,8 @@ xv36be              2261a0e3db5ee607d37f68d19704ae15
 xv36le              9202133de91bf64c76ca27d5cd0c816a
 xv48be              14373b7fe123225689e76fe2ce43fb93
 xv48le              319df9724a067c7b5efa215f9f54d127
-xyz12be             fc838024ab1eebf3a400e8a4a1f980d9
-xyz12le             33ab866ecb98fe6d028123ea9906e9a6
+xyz12be             8ac923c86bd11a04b049d083d857d116
+xyz12le             77912a89e6525f6809d8c31fed87a439
 ya16be              9bb39f9567d807be0fb8631dcfa8d5f8
 ya16le              b13453818b83c8dc2610975095393ac1
 ya8                 d4b7a62f80681fa44c977ff3a64f4ce4
diff --git a/tests/ref/fate/filter-pixfmts-vflip 
b/tests/ref/fate/filter-pixfmts-vflip
index e79832a6a93..10f745bb9c5 100644
--- a/tests/ref/fate/filter-pixfmts-vflip
+++ b/tests/ref/fate/filter-pixfmts-vflip
@@ -117,8 +117,8 @@ xv36be              c0272372d3e1a59adb3931ee433a5d5b
 xv36le              ffe6ab75ebc09134c3451f8f6ef0d501
 xv48be              bdfc3217ae456b370dbdcf4d52606a3f
 xv48le              53dbebab73a66539c04644ef56dc6bbb
-xyz12be             f0e4f326736c8158aeb89071e15f6688
-xyz12le             c7817e4c8da785850fa3269aec12c971
+xyz12be             0f14b1f9cfa04d9e1c945dd5057f3175
+xyz12le             e1b12cc0f304a5e62ac99ccf90f88756
 y210le              f8847bedd3ae6e1c0cf84a823f275e31
 y212le              c801725ae31e3b8f5be269359d49f191
 y216le              985db498aedf3fb1c547ad07442b7258
diff --git a/tests/ref/pixfmt/gbrp-xyz12le b/tests/ref/pixfmt/gbrp-xyz12le
index 7bffd86f93a..c11f079d07b 100644
--- a/tests/ref/pixfmt/gbrp-xyz12le
+++ b/tests/ref/pixfmt/gbrp-xyz12le
@@ -1,2 +1,2 @@
-8f95608cd59572ae0a5380da8b8518c6 *tests/data/pixfmt/gbrp-xyz12le.yuv
+8ff49367a8420d70a01e284b68aa6205 *tests/data/pixfmt/gbrp-xyz12le.yuv
 7603200 tests/data/pixfmt/gbrp-xyz12le.yuv
diff --git a/tests/ref/pixfmt/gbrp10-xyz12le b/tests/ref/pixfmt/gbrp10-xyz12le
index 58acebec6ef..ed1cbe6416a 100644
--- a/tests/ref/pixfmt/gbrp10-xyz12le
+++ b/tests/ref/pixfmt/gbrp10-xyz12le
@@ -1,2 +1,2 @@
-87e0d57bae18f4c69cf0594253a5e97c *tests/data/pixfmt/gbrp10-xyz12le.yuv
+5b3787cc3abeb28ac2045da52ad08a10 *tests/data/pixfmt/gbrp10-xyz12le.yuv
 15206400 tests/data/pixfmt/gbrp10-xyz12le.yuv
diff --git a/tests/ref/pixfmt/gbrp12-xyz12le b/tests/ref/pixfmt/gbrp12-xyz12le
index 7002d337712..0eab7993531 100644
--- a/tests/ref/pixfmt/gbrp12-xyz12le
+++ b/tests/ref/pixfmt/gbrp12-xyz12le
@@ -1,2 +1,2 @@
-882edf8efc53ffa9eb63e87a5acb8194 *tests/data/pixfmt/gbrp12-xyz12le.yuv
+66665b89e7e0ac71e6d3ee85300921ae *tests/data/pixfmt/gbrp12-xyz12le.yuv
 15206400 tests/data/pixfmt/gbrp12-xyz12le.yuv
diff --git a/tests/ref/pixfmt/nv24-xyz12le b/tests/ref/pixfmt/nv24-xyz12le
index 8aada49347d..76cd6b8b930 100644
--- a/tests/ref/pixfmt/nv24-xyz12le
+++ b/tests/ref/pixfmt/nv24-xyz12le
@@ -1,2 +1,2 @@
-890bbcd28ea3a06c315acb8c474215a0 *tests/data/pixfmt/nv24-xyz12le.yuv
+dc43783d32e1035f30c006eaa9fef0da *tests/data/pixfmt/nv24-xyz12le.yuv
 7603200 tests/data/pixfmt/nv24-xyz12le.yuv
diff --git a/tests/ref/pixfmt/p410-xyz12le b/tests/ref/pixfmt/p410-xyz12le
index 2fc2e6864aa..9ef4001198e 100644
--- a/tests/ref/pixfmt/p410-xyz12le
+++ b/tests/ref/pixfmt/p410-xyz12le
@@ -1,2 +1,2 @@
-ce3777038372083419a25923d9b6029f *tests/data/pixfmt/p410-xyz12le.yuv
+a31b9e74db3a4d230e924283ba0b365b *tests/data/pixfmt/p410-xyz12le.yuv
 15206400 tests/data/pixfmt/p410-xyz12le.yuv
diff --git a/tests/ref/pixfmt/p412-xyz12le b/tests/ref/pixfmt/p412-xyz12le
index 0815cbae13a..abf6bfe66aa 100644
--- a/tests/ref/pixfmt/p412-xyz12le
+++ b/tests/ref/pixfmt/p412-xyz12le
@@ -1,2 +1,2 @@
-7f1ac958f69ba454d23d667ecb4bc035 *tests/data/pixfmt/p412-xyz12le.yuv
+23f380ee860ab3f6af50228d5ead1a2c *tests/data/pixfmt/p412-xyz12le.yuv
 15206400 tests/data/pixfmt/p412-xyz12le.yuv
diff --git a/tests/ref/pixfmt/rgb24-xyz12le b/tests/ref/pixfmt/rgb24-xyz12le
index 6bd3308e5a9..f0374d57283 100644
--- a/tests/ref/pixfmt/rgb24-xyz12le
+++ b/tests/ref/pixfmt/rgb24-xyz12le
@@ -1,2 +1,2 @@
-6c138c678af6e0002167d0b088cbc57a *tests/data/pixfmt/rgb24-xyz12le.yuv
+c1b035b9c5438ae8307d94a855f3639e *tests/data/pixfmt/rgb24-xyz12le.yuv
 7603200 tests/data/pixfmt/rgb24-xyz12le.yuv
diff --git a/tests/ref/pixfmt/rgb48-xyz12le b/tests/ref/pixfmt/rgb48-xyz12le
index ee4b6cc2f0b..231f60a042f 100644
--- a/tests/ref/pixfmt/rgb48-xyz12le
+++ b/tests/ref/pixfmt/rgb48-xyz12le
@@ -1,2 +1,2 @@
-449f3253f683ea30518dcd345524a080 *tests/data/pixfmt/rgb48-xyz12le.yuv
+5013bb1fa0d6ceb6921110d719bd396c *tests/data/pixfmt/rgb48-xyz12le.yuv
 15206400 tests/data/pixfmt/rgb48-xyz12le.yuv
diff --git a/tests/ref/pixfmt/xyz12le b/tests/ref/pixfmt/xyz12le
index fd11596b959..5555dcff640 100644
--- a/tests/ref/pixfmt/xyz12le
+++ b/tests/ref/pixfmt/xyz12le
@@ -1,2 +1,2 @@
-8c8f1b04b0fe5aab4fc4f0612b4c3c09 *tests/data/pixfmt/xyz12le.yuv
+2e8a8e94dc351f2da89471a8afd87165 *tests/data/pixfmt/xyz12le.yuv
 304128 tests/data/pixfmt/xyz12le.yuv
diff --git a/tests/ref/pixfmt/yuv444p-xyz12le b/tests/ref/pixfmt/yuv444p-xyz12le
index e3410a669d7..91d4f7fc812 100644
--- a/tests/ref/pixfmt/yuv444p-xyz12le
+++ b/tests/ref/pixfmt/yuv444p-xyz12le
@@ -1,2 +1,2 @@
-35306788a7a59819bff4dade8c89ddff *tests/data/pixfmt/yuv444p-xyz12le.yuv
+aa8bffbcda1f890d29d4b70b155a0bbd *tests/data/pixfmt/yuv444p-xyz12le.yuv
 7603200 tests/data/pixfmt/yuv444p-xyz12le.yuv
diff --git a/tests/ref/pixfmt/yuv444p10-xyz12le 
b/tests/ref/pixfmt/yuv444p10-xyz12le
index 38c82d754fe..fd63b1ab22f 100644
--- a/tests/ref/pixfmt/yuv444p10-xyz12le
+++ b/tests/ref/pixfmt/yuv444p10-xyz12le
@@ -1,2 +1,2 @@
-b03201a536e152bff73cdbb9266ed948 *tests/data/pixfmt/yuv444p10-xyz12le.yuv
+78ed7dcd55f9a9c73247d2abcdef36a7 *tests/data/pixfmt/yuv444p10-xyz12le.yuv
 15206400 tests/data/pixfmt/yuv444p10-xyz12le.yuv
diff --git a/tests/ref/pixfmt/yuv444p12-xyz12le 
b/tests/ref/pixfmt/yuv444p12-xyz12le
index ebc9105afdd..4d8985a075a 100644
--- a/tests/ref/pixfmt/yuv444p12-xyz12le
+++ b/tests/ref/pixfmt/yuv444p12-xyz12le
@@ -1,2 +1,2 @@
-d9072d39396287f67eb38a1fec1abc6e *tests/data/pixfmt/yuv444p12-xyz12le.yuv
+9d0d5fb133d1b303688d444b16b4bd5b *tests/data/pixfmt/yuv444p12-xyz12le.yuv
 15206400 tests/data/pixfmt/yuv444p12-xyz12le.yuv
-- 
2.54.0


From b472cf21644c32ebaf28c78221a6f8bf552233ce Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sat, 24 Oct 2026 19:53:15 +0200
Subject: [PATCH 10/13] avutil/csp: apply only the transfer in the ST 428-1
 EOTF
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The EOTF scaled X and Z by the X/Y and Z/Y of the DCI white, the
reference projector of RP 431-2, which ST 428-1 does not define: it
fixes the transfer alone, 52.37/48 above the 48 cd/m2 white. Treat the
three channels alike, L = (Lw - Lb) * 52.37/48 * V^2.6 + Lb, through the
wrappers of the other SDR curves. Equal in each channel, the black is
neutral in RGB only. The black of X'Y'Z' has the white of its content,
which only the RGB it is decoded into knows, so the documentation asks
for Lb = 0 on X'Y'Z' samples and their black after that conversion. A
gray of the reference projector came out at the chromaticity (0.298,
0.366) instead of its own. Broken since 06f084468e.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libavutil/csp.c            | 30 ++----------------------------
 libavutil/csp.h            |  5 +++--
 tests/ref/fate/color_utils | 20 ++++++++++----------
 3 files changed, 15 insertions(+), 40 deletions(-)

diff --git a/libavutil/csp.c b/libavutil/csp.c
index 7ebbcc58560..5f379844292 100644
--- a/libavutil/csp.c
+++ b/libavutil/csp.c
@@ -537,6 +537,7 @@ static void oetf_##name##_inv(double E[3])                  
        \
 WRAP_SDR_OETF(gamma22)
 WRAP_SDR_OETF(gamma28)
 WRAP_SDR_OETF(iec61966_2_1)
+WRAP_SDR_OETF(smpte_st428_1)
 
 #define WRAP_SDR_EOTF(name)                                         \
 static void eotf_##name(double Lw, double Lb, double E[3])          \
@@ -554,6 +555,7 @@ static void eotf_##name##_inv(double Lw, double Lb, double 
L[3])    \
 WRAP_SDR_EOTF(gamma22)
 WRAP_SDR_EOTF(gamma28)
 WRAP_SDR_EOTF(iec61966_2_1)
+WRAP_SDR_EOTF(smpte_st428_1)
 
 static void eotf_bt1886(const double Lw, const double Lb, double E[3])
 {
@@ -589,34 +591,6 @@ static void eotf_smpte_st2084_inv(const double Lw, const 
double Lb, double L[3])
         L[i] = trc_smpte_st2084(L[i]);
 }
 
-/* This implementation assumes an SMPTE RP 431-2 reference projector (DCI) */
-#define DCI_L 48.00
-#define DCI_P 52.37
-#define DCI_X (42.94 / DCI_L)
-#define DCI_Z (45.82 / DCI_L)
-
-static void eotf_smpte_st428_1(const double Lw_Y, const double Lb_Y, double 
E[3])
-{
-    const double Lw[3] = { DCI_X * Lw_Y, Lw_Y, DCI_Z * Lw_Y };
-    const double Lb[3] = { DCI_X * Lb_Y, Lb_Y, DCI_Z * Lb_Y };
-
-    for (int i = 0; i < 3; i++) {
-        E[i] = (0.0 > E[i]) ? 0.0 : pow(E[i], 2.6) * DCI_P / DCI_L;
-        E[i] = E[i] * (Lw[i] - Lb[i]) + Lb[i];
-    }
-}
-
-static void eotf_smpte_st428_1_inv(const double Lw_Y, const double Lb_Y, 
double L[3])
-{
-    const double Lw[3] = { DCI_X * Lw_Y, Lw_Y, DCI_Z * Lw_Y };
-    const double Lb[3] = { DCI_X * Lb_Y, Lb_Y, DCI_Z * Lb_Y };
-
-    for (int i = 0; i < 3; i++) {
-        L[i] = (L[i] - Lb[i]) / (Lw[i] - Lb[i]);
-        L[i] = (0.0 > L[i]) ? 0.0 : pow(L[i] * DCI_L / DCI_P, 1.0 / 2.6);
-    }
-}
-
 static void eotf_arib_std_b67(const double Lw, const double Lb, double E[3])
 {
     const double gamma = fmax(1.2 + 0.42 * log10(Lw / 1000.0), 1.0);
diff --git a/libavutil/csp.h b/libavutil/csp.h
index 9d75ccbb640..8c035b46861 100644
--- a/libavutil/csp.h
+++ b/libavutil/csp.h
@@ -180,8 +180,9 @@ typedef void (*av_csp_eotf_function)(double Lw, double Lb, 
double c[3]);
 /**
  * Returns the ITU EOTF corresponding to a given TRC. This converts from the
  * signal level [0,1] to the raw output display luminance in nits (cd/m^2).
- * This is done per channel in RGB space, except for AVCOL_TRC_SMPTE428, which
- * assumes CIE XYZ in- and output.
+ * This is done per channel in RGB space. For X'Y'Z' with AVCOL_TRC_SMPTE428,
+ * pass Lb = 0 and apply the black level after the conversion to RGB for 
correct
+ * white point scaling.
  *
  * @return A pointer to the function implementing the given TRC, or NULL if no
  *         such function is defined.
diff --git a/tests/ref/fate/color_utils b/tests/ref/fate/color_utils
index e681ef73106..237c9ac40ff 100644
--- a/tests/ref/fate/color_utils
+++ b/tests/ref/fate/color_utils
@@ -2741,16 +2741,16 @@ trc=bt2020-12 EOTF(10000, 1.5, {0.7}) = {4360.83}
 trc=bt2020-12 EOTF(10000, 1.5, {0.8}) = {5943.5}
 trc=bt2020-12 EOTF(10000, 1.5, {0.9}) = {7818.66}
 trc=bt2020-12 EOTF(10000, 1.5, {1}) = {10000}
-trc=smpte428 EOTF(48.024, 0.024012, {0.0925519, 0.0967033, 0.0949939}) = 
{0.11771, 0.144579 0.132798}, expected Y=0.140000
-trc=smpte428 EOTF(48.024, 0.024012, {0.185348, 0.193407, 0.189988}) = 
{0.606907, 0.754996 0.689089}, expected Y=0.750000
-trc=smpte428 EOTF(48.024, 0.024012, {0.2779, 0.29011, 0.284982}) = {1.69956, 
2.12172 1.93462}, expected Y=2.120000
-trc=smpte428 EOTF(48.024, 0.024012, {0.370696, 0.386813, 0.379976}) = 
{3.57084, 4.45587 4.0618}, expected Y=4.450000
-trc=smpte428 EOTF(48.024, 0.024012, {0.463248, 0.483516, 0.474969}) = 
{6.35753, 7.94085 7.23777}, expected Y=7.940000
-trc=smpte428 EOTF(48.024, 0.024012, {0.5558, 0.58022, 0.569963}) = {10.1955, 
12.7421 11.6133}, expected Y=12.740000
-trc=smpte428 EOTF(48.024, 0.024012, {0.648596, 0.676923, 0.664957}) = 
{15.2212, 19.0123 17.3275}, expected Y=19.010000
-trc=smpte428 EOTF(48.024, 0.024012, {0.741148, 0.773626, 0.759951}) = 
{21.5223, 26.8938 24.5101}, expected Y=26.890000
-trc=smpte428 EOTF(48.024, 0.024012, {0.833944, 0.87033, 0.854945}) = {29.2401, 
36.5213 33.284}, expected Y=36.520000
-trc=smpte428 EOTF(48.024, 0.024012, {0.926496, 0.967033, 0.949939}) = 
{38.4361, 48.0228 43.7656}, expected Y=48.020000
+trc=smpte428 EOTF(48.024, 0.024012, {0.0925519, 0.0967033, 0.0949939}) = 
{0.13158, 0.144579 0.139116}, expected Y=0.140000
+trc=smpte428 EOTF(48.024, 0.024012, {0.185348, 0.193407, 0.189988}) = 
{0.678424, 0.754996 0.721874}, expected Y=0.750000
+trc=smpte428 EOTF(48.024, 0.024012, {0.2779, 0.29011, 0.284982}) = {1.89984, 
2.12172 2.02667}, expected Y=2.120000
+trc=smpte428 EOTF(48.024, 0.024012, {0.370696, 0.386813, 0.379976}) = 
{3.99162, 4.45587 4.25505}, expected Y=4.450000
+trc=smpte428 EOTF(48.024, 0.024012, {0.463248, 0.483516, 0.474969}) = 
{7.10669, 7.94085 7.58213}, expected Y=7.940000
+trc=smpte428 EOTF(48.024, 0.024012, {0.5558, 0.58022, 0.569963}) = {11.3969, 
12.7421 12.1659}, expected Y=12.740000
+trc=smpte428 EOTF(48.024, 0.024012, {0.648596, 0.676923, 0.664957}) = 
{17.0149, 19.0123 18.1519}, expected Y=19.010000
+trc=smpte428 EOTF(48.024, 0.024012, {0.741148, 0.773626, 0.759951}) = 
{24.0585, 26.8938 25.6763}, expected Y=26.890000
+trc=smpte428 EOTF(48.024, 0.024012, {0.833944, 0.87033, 0.854945}) = {32.6857, 
36.5213 34.8675}, expected Y=36.520000
+trc=smpte428 EOTF(48.024, 0.024012, {0.926496, 0.967033, 0.949939}) = 
{42.9653, 48.0228 45.8478}, expected Y=48.020000
 trc=arib-std-b67 EOTF(50, 0, {0}) = {0}
 trc=arib-std-b67 EOTF(50, 0, {0.1}) = {0.166667}
 trc=arib-std-b67 EOTF(50, 0, {0.2}) = {0.666667}
-- 
2.54.0


From 43f8c1674b79e050195e04536947cbd73c9a2084 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sat, 24 Oct 2026 19:53:16 +0200
Subject: [PATCH 11/13] tests/color_utils: check that the ST 428-1 EOTF scales
 X, Y and Z correctly
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libavutil/tests/color_utils.c |  5 +++++
 tests/ref/fate/color_utils    | 10 ++++++++++
 2 files changed, 15 insertions(+)

diff --git a/libavutil/tests/color_utils.c b/libavutil/tests/color_utils.c
index f96955082f4..ad41dde2731 100644
--- a/libavutil/tests/color_utils.c
+++ b/libavutil/tests/color_utils.c
@@ -128,6 +128,11 @@ int main(int argc, char *argv[])
                     printf("  FAIL\n");
                     return 1;
                 }
+
+                memcpy(L_xyz, tests[i].E_xyz, sizeof(L_xyz));
+                eotf(Lw, 0.0, L_xyz);
+                printf("  ratios X/Y=%.9f Z/Y=%.9f\n",
+                       L_xyz[0] / L_xyz[1], L_xyz[2] / L_xyz[1]);
             }
         } else {
             /* Normal, display-relative RGB curve */
diff --git a/tests/ref/fate/color_utils b/tests/ref/fate/color_utils
index 237c9ac40ff..07a61c313ec 100644
--- a/tests/ref/fate/color_utils
+++ b/tests/ref/fate/color_utils
@@ -2742,15 +2742,25 @@ trc=bt2020-12 EOTF(10000, 1.5, {0.8}) = {5943.5}
 trc=bt2020-12 EOTF(10000, 1.5, {0.9}) = {7818.66}
 trc=bt2020-12 EOTF(10000, 1.5, {1}) = {10000}
 trc=smpte428 EOTF(48.024, 0.024012, {0.0925519, 0.0967033, 0.0949939}) = 
{0.13158, 0.144579 0.139116}, expected Y=0.140000
+  ratios X/Y=0.892184066 Z/Y=0.954688036
 trc=smpte428 EOTF(48.024, 0.024012, {0.185348, 0.193407, 0.189988}) = 
{0.678424, 0.754996 0.721874}, expected Y=0.750000
+  ratios X/Y=0.895247558 Z/Y=0.954688036
 trc=smpte428 EOTF(48.024, 0.024012, {0.2779, 0.29011, 0.284982}) = {1.89984, 
2.12172 2.02667}, expected Y=2.120000
+  ratios X/Y=0.894225676 Z/Y=0.954688036
 trc=smpte428 EOTF(48.024, 0.024012, {0.370696, 0.386813, 0.379976}) = 
{3.99162, 4.45587 4.25505}, expected Y=4.450000
+  ratios X/Y=0.895247558 Z/Y=0.954688036
 trc=smpte428 EOTF(48.024, 0.024012, {0.463248, 0.483516, 0.474969}) = 
{7.10669, 7.94085 7.58213}, expected Y=7.940000
+  ratios X/Y=0.894634343 Z/Y=0.954688036
 trc=smpte428 EOTF(48.024, 0.024012, {0.5558, 0.58022, 0.569963}) = {11.3969, 
12.7421 12.1659}, expected Y=12.740000
+  ratios X/Y=0.894225676 Z/Y=0.954688036
 trc=smpte428 EOTF(48.024, 0.024012, {0.648596, 0.676923, 0.664957}) = 
{17.0149, 19.0123 18.1519}, expected Y=19.010000
+  ratios X/Y=0.894809521 Z/Y=0.954688036
 trc=smpte428 EOTF(48.024, 0.024012, {0.741148, 0.773626, 0.759951}) = 
{24.0585, 26.8938 25.6763}, expected Y=26.890000
+  ratios X/Y=0.894481079 Z/Y=0.954688036
 trc=smpte428 EOTF(48.024, 0.024012, {0.833944, 0.87033, 0.854945}) = {32.6857, 
36.5213 34.8675}, expected Y=36.520000
+  ratios X/Y=0.894906851 Z/Y=0.954688036
 trc=smpte428 EOTF(48.024, 0.024012, {0.926496, 0.967033, 0.949939}) = 
{42.9653, 48.0228 45.8478}, expected Y=48.020000
+  ratios X/Y=0.894634343 Z/Y=0.954688036
 trc=arib-std-b67 EOTF(50, 0, {0}) = {0}
 trc=arib-std-b67 EOTF(50, 0, {0.1}) = {0.166667}
 trc=arib-std-b67 EOTF(50, 0, {0.2}) = {0.666667}
-- 
2.54.0


From 903735b4469c8be0037d5a55623d5d64f0b623f1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sun, 25 Oct 2026 06:03:37 +0100
Subject: [PATCH 12/13] swscale: build the X'Y'Z' tables from the transfer
 functions of lavu
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The tables were computed from the exponents 2.6 and 2.2 written into
the code. Build them from the lavu functions of SMPTE ST 428-1 and
gamma 2.2 instead, normalized to the top code of each curve with the
peaks in the matrix as before, so that the conversion can be set up for
any tagged transfer. The values are unchanged.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libswscale/format.h           |  4 +++
 libswscale/swscale_internal.h |  3 --
 libswscale/utils.c            | 59 ++++++++++++++++++++---------------
 3 files changed, 37 insertions(+), 29 deletions(-)

diff --git a/libswscale/format.h b/libswscale/format.h
index 8174cd18913..cf8068c4554 100644
--- a/libswscale/format.h
+++ b/libswscale/format.h
@@ -57,6 +57,10 @@ enum {
     FIELD_BOTTOM, /* bottom/odd rows */
 };
 
+/* The X'Y'Z' conversion: ST 428-1 codes to and from gamma 2.2 RGB */
+#define XYZ_TRC     AVCOL_TRC_SMPTE428
+#define XYZ_RGB_TRC AVCOL_TRC_GAMMA22
+
 typedef struct SwsColor {
     enum AVColorPrimaries prim;
     enum AVColorTransferCharacteristic trc;
diff --git a/libswscale/swscale_internal.h b/libswscale/swscale_internal.h
index 5d641fb02a4..dd296f30d12 100644
--- a/libswscale/swscale_internal.h
+++ b/libswscale/swscale_internal.h
@@ -552,9 +552,6 @@ struct SwsInternal {
 
     int use_mmx_vfilter;
 
-/* pre defined color-spaces gamma */
-#define XYZ_GAMMA (2.6)
-#define RGB_GAMMA (2.2)
     SwsColorFunc  xyz12Torgb48;
     SwsColorFunc  rgb48Toxyz12;
     SwsColorXform xyz2rgb;
diff --git a/libswscale/utils.c b/libswscale/utils.c
index 39609915632..c148813cd14 100644
--- a/libswscale/utils.c
+++ b/libswscale/utils.c
@@ -723,32 +723,40 @@ static void fill_rgb2yuv_table(SwsInternal *c, const int 
table[4], int dstRange)
         AV_WL16(p + 16*4 + 2*i, map[i] >= 0 ? c->input_rgb2yuv_table[map[i]] : 
0);
 }
 
-#if CONFIG_SMALL
-static void init_xyz_tables(uint16_t xyzgamma_tab[4096],  uint16_t 
xyzgammainv_tab[65536],
-                            uint16_t rgbgamma_tab[65536], uint16_t 
rgbgammainv_tab[4096])
-#else
+static double trc_peak(enum AVColorTransferCharacteristic trc)
+{
+    return av_csp_trc_func_inv_from_id(trc)(1.0);
+}
+
+static void init_xyz_luts(SwsLuts *luts, enum AVColorTransferCharacteristic 
trc_in,
+                          enum AVColorTransferCharacteristic trc_out)
+{
+    const av_csp_trc_function to_linear   = 
av_csp_trc_func_inv_from_id(trc_in);
+    const av_csp_trc_function from_linear = av_csp_trc_func_from_id(trc_out);
+    const double peak_in = trc_peak(trc_in), peak_out = trc_peak(trc_out);
+
+    for (int i = 0; i < 4096; i++)
+        luts->in[i] = lrint(to_linear(i / 4095.0) / peak_in * 65535.0);
+    for (int i = 0; i < 65536; i++)
+        luts->out[i] = lrint(from_linear(i / 65535.0 * peak_out) * 4095.0);
+}
+
+static void init_xyz_tables(SwsLuts *xyz2rgb, SwsLuts *rgb2xyz)
+{
+    init_xyz_luts(xyz2rgb, XYZ_TRC, XYZ_RGB_TRC);
+    init_xyz_luts(rgb2xyz, XYZ_RGB_TRC, XYZ_TRC);
+}
+
+#if !CONFIG_SMALL
 static uint16_t xyzgamma_tab[4096],  rgbgammainv_tab[4096];
 static uint16_t rgbgamma_tab[65536], xyzgammainv_tab[65536];
-static av_cold void init_xyz_tables(void)
-#endif
+static av_cold void init_xyz_tables_once(void)
 {
-    double xyzgamma    = XYZ_GAMMA;
-    double rgbgamma    = 1.0 / RGB_GAMMA;
-    double xyzgammainv = 1.0 / XYZ_GAMMA;
-    double rgbgammainv = RGB_GAMMA;
-
-    /* set input gamma vectors */
-    for (int i = 0; i < 4096; i++) {
-        xyzgamma_tab[i]    = lrint(pow(i / 4095.0, xyzgamma)    * 65535.0);
-        rgbgammainv_tab[i] = lrint(pow(i / 4095.0, rgbgammainv) * 65535.0);
-    }
-
-    /* set output gamma vectors */
-    for (int i = 0; i < 65536; i++) {
-        rgbgamma_tab[i]    = lrint(pow(i / 65535.0, rgbgamma)    * 4095.0);
-        xyzgammainv_tab[i] = lrint(pow(i / 65535.0, xyzgammainv) * 4095.0);
-    }
+    SwsLuts xyz2rgb = { xyzgamma_tab,    rgbgamma_tab    };
+    SwsLuts rgb2xyz = { rgbgammainv_tab, xyzgammainv_tab };
+    init_xyz_tables(&xyz2rgb, &rgb2xyz);
 }
+#endif
 
 static void fill_xyz_matrix(int16_t mat[3][3], const SwsMatrix3x3 *m, double 
scale)
 {
@@ -765,7 +773,7 @@ av_cold int ff_sws_fill_st428_tables(SwsInternal *c)
 {
     /* X'Y'Z' puts its 48 cd/m2 white at code 3960 and the top code at
      * 52.37 cd/m2 (ST 428-1), the codes above the white clip in RGB */
-    const double peak = av_csp_trc_func_inv_from_id(AVCOL_TRC_SMPTE428)(1.0);
+    const double peak = trc_peak(XYZ_TRC) / trc_peak(XYZ_RGB_TRC);
     const AVColorPrimariesDesc *bt709 = 
av_csp_primaries_desc_from_id(AVCOL_PRI_BT709);
     const SwsMatrix3x3 xyz2rgb = ff_sws_xyz2rgb(bt709);
     const SwsMatrix3x3 rgb2xyz = ff_sws_rgb2xyz(bt709);
@@ -783,8 +791,7 @@ av_cold int ff_sws_fill_st428_tables(SwsInternal *c)
     c->rgb2xyz.gamma.in  = c->xyz2rgb.gamma.in  + 4096;
     c->xyz2rgb.gamma.out = c->rgb2xyz.gamma.in  + 4096;
     c->rgb2xyz.gamma.out = c->xyz2rgb.gamma.out + 65536;
-    init_xyz_tables(c->xyz2rgb.gamma.in,  c->rgb2xyz.gamma.out,
-                    c->xyz2rgb.gamma.out, c->rgb2xyz.gamma.in);
+    init_xyz_tables(&c->xyz2rgb.gamma, &c->rgb2xyz.gamma);
 #else
     c->xyz2rgb.gamma.in  = xyzgamma_tab;
     c->xyz2rgb.gamma.out = rgbgamma_tab;
@@ -792,7 +799,7 @@ av_cold int ff_sws_fill_st428_tables(SwsInternal *c)
     c->rgb2xyz.gamma.out = xyzgammainv_tab;
 
     static AVOnce xyz_init_static_once = AV_ONCE_INIT;
-    ff_thread_once(&xyz_init_static_once, init_xyz_tables);
+    ff_thread_once(&xyz_init_static_once, init_xyz_tables_once);
 #endif
     return 0;
 }
-- 
2.54.0


From ca36298b492693f270a81c452940c142e49a43cc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sun, 25 Oct 2026 06:03:38 +0100
Subject: [PATCH 13/13] swscale: scale X'Y'Z' to X'Y'Z' on the codes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

A scaled conversion between two XYZ formats decoded the codes into
gamma 2.2 RGB, scaled that and encoded it back, which quantizes twice
and clips every color outside the BT.709 gamut. Scale the codes like
those of an RGB format instead, as the unscaled conversion already did,
and round the result to 12 bits, which keeps the 4 padding bits zero.
The slice contexts of the graph take the XYZ flags of their parent for
that.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libswscale/graph.c                  |  8 +++++---
 libswscale/swscale.c                | 26 ++++++++++++++++++++++++--
 tests/ref/fate/filter-pixfmts-scale |  4 ++--
 3 files changed, 31 insertions(+), 7 deletions(-)

diff --git a/libswscale/graph.c b/libswscale/graph.c
index cc8e5cf7a23..e9ee32052aa 100644
--- a/libswscale/graph.c
+++ b/libswscale/graph.c
@@ -450,7 +450,6 @@ static int init_legacy_subpass(SwsGraph *graph, SwsContext 
*sws,
     SwsInternal *c = sws_internal(sws);
     const int src_w = sws->src_w, src_h = sws->src_h;
     const int dst_w = sws->dst_w, dst_h = sws->dst_h;
-    const int unscaled = src_w == dst_w && src_h == dst_h;
     int align = c->dst_slice_align;
     SwsPass *pass = NULL;
     int ret;
@@ -485,7 +484,7 @@ static int init_legacy_subpass(SwsGraph *graph, SwsContext 
*sws,
         }
     }
 
-    if (c->srcXYZ && !(c->dstXYZ && unscaled)) {
+    if (c->srcXYZ && !c->dstXYZ) {
         ret = ff_sws_graph_add_pass(graph, AV_PIX_FMT_RGB48, src_w, src_h, 
input,
                                     0, 1, run_xyz2rgb, NULL, c, NULL, &input);
         if (ret < 0) {
@@ -541,10 +540,13 @@ static int init_legacy_subpass(SwsGraph *graph, 
SwsContext *sws,
                 c2->srcColorspaceTable[i] = c->srcColorspaceTable[i];
                 c2->dstColorspaceTable[i] = c->dstColorspaceTable[i];
             }
+
+            c2->srcXYZ = c->srcXYZ;
+            c2->dstXYZ = c->dstXYZ;
         }
     }
 
-    if (c->dstXYZ && !(c->srcXYZ && unscaled)) {
+    if (c->dstXYZ && !c->srcXYZ) {
         ret = ff_sws_graph_add_pass(graph, AV_PIX_FMT_RGB48, dst_w, dst_h, 
pass,
                                     0, 1, run_rgb2xyz, NULL, c, NULL, &pass);
         if (ret < 0)
diff --git a/libswscale/swscale.c b/libswscale/swscale.c
index 3b2c63cff0c..0ba3458b6d1 100644
--- a/libswscale/swscale.c
+++ b/libswscale/swscale.c
@@ -254,6 +254,23 @@ static void lumRangeFromJpeg16_c(int16_t *_dst, int width,
         dst[i] = ((int64_t) dst[i] * coeff + offset) >> 18;
 }
 
+static void round_xyz12(uint8_t *plane, int stride, int width, int height, int 
y,
+                        int big_endian)
+{
+    uint8_t *ptr = plane + stride * y;
+    for (int i = 0; i < height; i++) {
+        for (int j = 0; j < 3 * width; j++) {
+            unsigned v = big_endian ? AV_RB16(ptr + 2 * j) : AV_RL16(ptr + 2 * 
j);
+            v = FFMIN(v + 8, UINT16_MAX) & 0xFFF0;
+            if (big_endian)
+                AV_WB16(ptr + 2 * j, v);
+            else
+                AV_WL16(ptr + 2 * j, v);
+        }
+        ptr += stride;
+    }
+}
+
 
 #define DEBUG_SWSCALE_BUFFERS 0
 #define DEBUG_BUFFERS(...)                      \
@@ -552,6 +569,11 @@ int ff_swscale(SwsInternal *c, const uint8_t *const src[], 
const int srcStride[]
             fillPlane(dst[3], dstStride[3], length, height, offset, 255);
     }
 
+    /* XYZ is scaled on its codes like 16-bit RGB */
+    if (c->srcXYZ && c->dstXYZ)
+        round_xyz12(dst[0], dstStride[0], dstW, dstY - lastDstY, lastDstY - 
dstSliceY,
+                    isBE(dstFormat));
+
 #if HAVE_MMXEXT_INLINE
     if (av_get_cpu_flags() & AV_CPU_FLAG_MMXEXT)
         __asm__ volatile ("sfence" ::: "memory");
@@ -1123,7 +1145,7 @@ static int scale_internal(SwsContext *sws,
         src2[0] = base;
     }
 
-    if (c->srcXYZ && !(c->dstXYZ && sws->src_w==sws->dst_w && 
sws->src_h==sws->dst_h)) {
+    if (c->srcXYZ && !c->dstXYZ) {
         uint8_t *base;
 
         av_fast_malloc(&c->xyz_scratch, &c->xyz_scratch_allocated,
@@ -1191,7 +1213,7 @@ static int scale_internal(SwsContext *sws,
                          dst2, dstStride2, dstSliceY, dstSliceH);
     }
 
-    if (c->dstXYZ && !(c->srcXYZ && sws->src_w==sws->dst_w && 
sws->src_h==sws->dst_h)) {
+    if (c->dstXYZ && !c->srcXYZ) {
         uint8_t *dst;
 
         if (scale_dst) {
diff --git a/tests/ref/fate/filter-pixfmts-scale 
b/tests/ref/fate/filter-pixfmts-scale
index 3a7b34ad963..900227f7c35 100644
--- a/tests/ref/fate/filter-pixfmts-scale
+++ b/tests/ref/fate/filter-pixfmts-scale
@@ -117,8 +117,8 @@ xv36be              4d084adca0228d7750d1e2e877e0d79b
 xv36le              de9c74e94dc19c828e1572aa283d8aca
 xv48be              9e58d1a045df100b0dec116e13be5b4e
 xv48le              fd873d53609b2fbdfe99470f515a234c
-xyz12be             0866f8e5493ea93f4523a9e873a14b1e
-xyz12le             d6990efe1c7141227054cbcda28ea6fb
+xyz12be             5390b76c745f71cbd6db031bd424c941
+xyz12le             aba95a389ce26cb603ffe4072d1791d9
 y210le              7c2aef142d88ab343ec01acd45f38466
 y212le              39a3c0c843041ad4501b3107dd91ef17
 y216le              17be2999e97d36b8ed903f07ef428c09
-- 
2.54.0

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

Reply via email to