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]
