PR #25423 opened by Kacper Michajłow (kasper93) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25423 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25423.patch
From 492b44eb3f6a5075ba85be17597fd9248afb2a7b 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 1/3] 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 334e3d69e62e37b8c9ca09ddabba2a28402e244a 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 2/3] 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 ef97881c33f083b646ff83e17172e661b4f8b3b8 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 3/3] 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 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
