mirror of https://github.com/mpv-player/mpv
video: Refactor rendering intent for DCP XYZ and :srgb
Notably, we now conform to SMPTE 428-1-2006 when decoding XYZ12 input, and we can support rendering intents other than colorimetric when converting between BT.709 and BT.2020, like with :srgb or :icc-profile.
This commit is contained in:
parent
7f3ea12802
commit
fbd35caef8
|
@ -505,6 +505,10 @@ Available video output drivers are:
|
|||
Its size depends on the ``3dlut-size``, and can be very big.
|
||||
|
||||
``icc-intent=<value>``
|
||||
Specifies the ICC Intent used for transformations between colorspaces.
|
||||
This affects the rendering when using ``icc-profile`` or ``srgb`` and
|
||||
also affects the way DCP XYZ content gets converted to RGB.
|
||||
|
||||
0
|
||||
perceptual
|
||||
1
|
||||
|
|
130
video/csputils.c
130
video/csputils.c
|
@ -342,60 +342,81 @@ void mp_get_rgb2xyz_matrix(struct mp_csp_primaries space, float m[3][3])
|
|||
}
|
||||
}
|
||||
|
||||
// M := M * XYZd<-XYZs
|
||||
void mp_apply_chromatic_adaptation(struct mp_csp_col_xy src, struct mp_csp_col_xy dest, float m[3][3])
|
||||
{
|
||||
// If the white points are nearly identical, this is a wasteful identity
|
||||
// operation.
|
||||
if (fabs(src.x - dest.x) < 1e-6 && fabs(src.y - dest.y) < 1e-6)
|
||||
return;
|
||||
|
||||
// XYZd<-XYZs = Ma^-1 * (I*[Cd/Cs]) * Ma
|
||||
// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
|
||||
float C[3][2], tmp[3][3] = {{0}};
|
||||
|
||||
// Ma = Bradford matrix, arguably most popular method in use today.
|
||||
// This is derived experimentally and thus hard-coded.
|
||||
float bradford[3][3] = {
|
||||
{ 0.8951, 0.2664, -0.1614 },
|
||||
{ -0.7502, 1.7135, 0.0367 },
|
||||
{ 0.0389, -0.0685, 1.0296 },
|
||||
};
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
// source cone
|
||||
C[i][0] = bradford[i][0] * src.x / src.y
|
||||
+ bradford[i][1] * 1
|
||||
+ bradford[i][2] * (1 - src.x - src.y) / src.y;
|
||||
|
||||
// dest cone
|
||||
C[i][1] = bradford[i][0] * dest.x / dest.y
|
||||
+ bradford[i][1] * 1
|
||||
+ bradford[i][2] * (1 - dest.x - dest.y) / dest.y;
|
||||
}
|
||||
|
||||
// tmp := I * [Cd/Cs] * Ma
|
||||
for (int i = 0; i < 3; i++)
|
||||
tmp[i][i] = C[i][1] / C[i][0];
|
||||
|
||||
mp_mul_matrix3x3(tmp, bradford);
|
||||
|
||||
// M := M * Ma^-1 * tmp
|
||||
mp_invert_matrix3x3(bradford);
|
||||
mp_mul_matrix3x3(m, bradford);
|
||||
mp_mul_matrix3x3(m, tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief get the coefficients of the source -> bt2020 cms matrix
|
||||
* \param src primaries of the source gamut
|
||||
* \param dest primaries of the destination gamut
|
||||
* \param intent rendering intent for the transformation
|
||||
* \param m array to store coefficients into
|
||||
*/
|
||||
void mp_get_cms_matrix(struct mp_csp_primaries src, struct mp_csp_primaries dest, float m[3][3])
|
||||
void mp_get_cms_matrix(struct mp_csp_primaries src, struct mp_csp_primaries dest, enum mp_render_intent intent, float m[3][3])
|
||||
{
|
||||
float tmp[3][3];
|
||||
|
||||
// In saturation mapping, we don't care about accuracy and just want
|
||||
// primaries to map to primaries, making this an identity transformation.
|
||||
if (intent == MP_INTENT_SATURATION) {
|
||||
for (int i = 0; i < 3; i++)
|
||||
m[i][i] = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
// RGBd<-RGBs = RGBd<-XYZd * XYZd<-XYZs * XYZs<-RGBs
|
||||
// Equations from: http://www.brucelindbloom.com/index.html?Math.html
|
||||
float tmp[3][3] = {{0}};
|
||||
// Note: Perceptual is treated like relative colorimetric. There's no
|
||||
// definition for perceptual other than "make it look good".
|
||||
|
||||
// RGBd<-XYZd, inverted from XYZd<-RGBd
|
||||
mp_get_rgb2xyz_matrix(dest, m);
|
||||
mp_invert_matrix3x3(m);
|
||||
|
||||
// Chromatic adaptation, only needed if the white point differs
|
||||
if (fabs(src.white.x - dest.white.x) > 1e-6 ||
|
||||
fabs(src.white.y - dest.white.y) > 1e-6) {
|
||||
// XYZd<-XYZs = Ma^-1 * (I*[Cd/Cs]) * Ma
|
||||
// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
|
||||
float C[3][2];
|
||||
|
||||
// Ma = Bradford matrix, arguably most popular method in use today.
|
||||
// This is derived experimentally and thus hard-coded.
|
||||
float bradford[3][3] = {
|
||||
{ 0.8951, 0.2664, -0.1614 },
|
||||
{ -0.7502, 1.7135, 0.0367 },
|
||||
{ 0.0389, -0.0685, 1.0296 },
|
||||
};
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
// source cone
|
||||
C[i][0] = bradford[i][0] * src.white.x / src.white.y
|
||||
+ bradford[i][1] * 1
|
||||
+ bradford[i][2] * (1 - src.white.x - src.white.y) / src.white.y;
|
||||
|
||||
// dest cone
|
||||
C[i][1] = bradford[i][0] * dest.white.x / dest.white.y
|
||||
+ bradford[i][1] * 1
|
||||
+ bradford[i][2] * (1 - dest.white.x - dest.white.y) / dest.white.y;
|
||||
}
|
||||
|
||||
// tmp := I * [Cd/Cs] * Ma
|
||||
for (int i = 0; i < 3; i++)
|
||||
tmp[i][i] = C[i][1] / C[i][0];
|
||||
|
||||
mp_mul_matrix3x3(tmp, bradford);
|
||||
|
||||
// M := M * Ma^-1 * tmp
|
||||
mp_invert_matrix3x3(bradford);
|
||||
mp_mul_matrix3x3(m, bradford);
|
||||
mp_mul_matrix3x3(m, tmp);
|
||||
}
|
||||
// Chromatic adaptation, except in absolute colorimetric intent
|
||||
if (intent != MP_INTENT_ABSOLUTE_COLORIMETRIC)
|
||||
mp_apply_chromatic_adaptation(src.white, dest.white, m);
|
||||
|
||||
// XYZs<-RGBs
|
||||
mp_get_rgb2xyz_matrix(src, tmp);
|
||||
|
@ -436,17 +457,25 @@ static void luma_coeffs(float m[3][4], float lr, float lg, float lb)
|
|||
}
|
||||
|
||||
/**
|
||||
* \brief get the coefficients of an xyz -> rgb conversion matrix
|
||||
* \brief get the coefficients of an SMPTE 428-1 xyz -> rgb conversion matrix
|
||||
* \param params parameters for the conversion, only brightness is used
|
||||
* \param prim primaries of the RGB space to transform to
|
||||
* \param intent the rendering intent used to convert to the target primaries
|
||||
* \param m array to store the coefficients into
|
||||
*/
|
||||
void mp_get_xyz2rgb_coeffs(struct mp_csp_params *params, struct mp_csp_primaries prim, float m[3][4])
|
||||
void mp_get_xyz2rgb_coeffs(struct mp_csp_params *params, struct mp_csp_primaries prim, enum mp_render_intent intent, float m[3][4])
|
||||
{
|
||||
float tmp[3][3], brightness = params->brightness;
|
||||
mp_get_rgb2xyz_matrix(prim, tmp);
|
||||
mp_invert_matrix3x3(tmp);
|
||||
|
||||
// All non-absolute mappings want to map source white to target white
|
||||
if (intent != MP_INTENT_ABSOLUTE_COLORIMETRIC) {
|
||||
// SMPTE 428-1 defines the calibration white point as CIE xy (0.314, 0.351)
|
||||
static const struct mp_csp_col_xy smpte428 = {0.314, 0.351};
|
||||
mp_apply_chromatic_adaptation(smpte428, prim.white, tmp);
|
||||
}
|
||||
|
||||
// Since this outputs linear RGB rather than companded RGB, we
|
||||
// want to linearize any brightness additions. 2 is a reasonable
|
||||
// approximation for any sort of gamma function that could be in use.
|
||||
|
@ -502,19 +531,10 @@ void mp_get_yuv2rgb_coeffs(struct mp_csp_params *params, float m[3][4])
|
|||
}
|
||||
case MP_CSP_XYZ: {
|
||||
// The vo should probably not be using a matrix generated by this
|
||||
// function for XYZ sources, but if it does, let's just assume we
|
||||
// want BT.709.
|
||||
float xyz_to_rgb[3][3];
|
||||
mp_get_rgb2xyz_matrix(mp_get_csp_primaries(MP_CSP_PRIM_BT_709), xyz_to_rgb);
|
||||
mp_invert_matrix3x3(xyz_to_rgb);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++)
|
||||
m[i][j] = xyz_to_rgb[i][j];
|
||||
|
||||
m[i][3] = 0;
|
||||
}
|
||||
|
||||
// function for XYZ sources, but if it does, let's just assume it
|
||||
// wants BT.709 with D65 white point (virtually all other content).
|
||||
mp_get_xyz2rgb_coeffs(params, mp_get_csp_primaries(MP_CSP_PRIM_BT_709),
|
||||
MP_INTENT_RELATIVE_COLORIMETRIC, m);
|
||||
levels_in = -1;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -70,6 +70,15 @@ enum mp_csp_prim {
|
|||
// Any enum mp_csp_prim value is a valid index (except MP_CSP_PRIM_COUNT)
|
||||
extern const char *const mp_csp_prim_names[MP_CSP_PRIM_COUNT];
|
||||
|
||||
// These constants are based on the ICC specification (Table 23) and match
|
||||
// up with the API of LittleCMS, which treats them as integers.
|
||||
enum mp_render_intent {
|
||||
MP_INTENT_PERCEPTUAL = 0,
|
||||
MP_INTENT_RELATIVE_COLORIMETRIC = 1,
|
||||
MP_INTENT_SATURATION = 2,
|
||||
MP_INTENT_ABSOLUTE_COLORIMETRIC = 3
|
||||
};
|
||||
|
||||
struct mp_csp_details {
|
||||
enum mp_csp format;
|
||||
enum mp_csp_levels levels_in; // encoded video
|
||||
|
@ -187,10 +196,13 @@ void mp_gen_gamma_map(unsigned char *map, int size, float gamma);
|
|||
#define COL_C 3
|
||||
struct mp_csp_primaries mp_get_csp_primaries(enum mp_csp_prim csp);
|
||||
|
||||
void mp_get_cms_matrix(struct mp_csp_primaries src, struct mp_csp_primaries dest, float cms_matrix[3][3]);
|
||||
void mp_apply_chromatic_adaptation(struct mp_csp_col_xy src, struct mp_csp_col_xy dest, float m[3][3]);
|
||||
void mp_get_cms_matrix(struct mp_csp_primaries src, struct mp_csp_primaries dest,
|
||||
enum mp_render_intent intent, float cms_matrix[3][3]);
|
||||
void mp_get_rgb2xyz_matrix(struct mp_csp_primaries space, float m[3][3]);
|
||||
|
||||
void mp_get_xyz2rgb_coeffs(struct mp_csp_params *params, struct mp_csp_primaries prim, float xyz2rgb[3][4]);
|
||||
void mp_get_xyz2rgb_coeffs(struct mp_csp_params *params, struct mp_csp_primaries prim,
|
||||
enum mp_render_intent intent, float xyz2rgb[3][4]);
|
||||
void mp_get_yuv2rgb_coeffs(struct mp_csp_params *params, float yuv2rgb[3][4]);
|
||||
void mp_gen_yuv2rgb_map(struct mp_csp_params *params, uint8_t *map, int size);
|
||||
|
||||
|
|
|
@ -577,7 +577,11 @@ static void update_uniforms(struct gl_video *p, GLuint program)
|
|||
if (loc >= 0) {
|
||||
float m[3][4] = {{0}};
|
||||
if (p->image_desc.flags & MP_IMGFLAG_XYZ) {
|
||||
mp_get_xyz2rgb_coeffs(&cparams, p->csp_src, m);
|
||||
// Hard-coded as relative colorimetric for now, since this transforms
|
||||
// from the source file's D55 material to whatever color space our
|
||||
// projector/display lives in, which should be D55 for a proper
|
||||
// home cinema setup either way.
|
||||
mp_get_xyz2rgb_coeffs(&cparams, p->csp_src, MP_INTENT_RELATIVE_COLORIMETRIC, m);
|
||||
} else {
|
||||
mp_get_yuv2rgb_coeffs(&cparams, m);
|
||||
}
|
||||
|
@ -649,7 +653,10 @@ static void update_uniforms(struct gl_video *p, GLuint program)
|
|||
loc = gl->GetUniformLocation(program, "cms_matrix");
|
||||
if (loc >= 0) {
|
||||
float cms_matrix[3][3] = {{0}};
|
||||
mp_get_cms_matrix(p->csp_src, p->csp_dest, cms_matrix);
|
||||
// Hard-coded to relative colorimetric - for a BT.2020 3DLUT we expect
|
||||
// the input to be actual BT.2020 and not something red- or blueshifted,
|
||||
// and for sRGB monitors we most likely want relative scaling either way.
|
||||
mp_get_cms_matrix(p->csp_src, p->csp_dest, MP_INTENT_RELATIVE_COLORIMETRIC, cms_matrix);
|
||||
gl->UniformMatrix3fv(loc, 1, GL_TRUE, &cms_matrix[0][0]);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue