avutil/csp: apply only the transfer in the ST 428-1 EOTF

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 <kasper93@gmail.com>
This commit is contained in:
Kacper Michajłow committed 2026-10-28 19:22:28 +00:00
1 parent 61ae1901c7
commit 89ca1261d0
3 files changed
+15 -40

No files matched your search

+2 -28
View File
@@ -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);
+3 -2
View File
@@ -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.
+10 -10
View File
@@ -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}