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:
Niklas Haas 2014-04-01 00:17:07 +02:00 committed by wm4
parent 7f3ea12802
commit fbd35caef8
4 changed files with 102 additions and 59 deletions

View File

@ -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

View File

@ -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;
}

View File

@ -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);

View File

@ -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]);
}