summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--DOCS/man/vo.rst4
-rw-r--r--video/csputils.c130
-rw-r--r--video/csputils.h16
-rw-r--r--video/out/gl_video.c11
4 files changed, 102 insertions, 59 deletions
diff --git a/DOCS/man/vo.rst b/DOCS/man/vo.rst
index dc4f49097c..587cdd2afd 100644
--- a/DOCS/man/vo.rst
+++ b/DOCS/man/vo.rst
@@ -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
diff --git a/video/csputils.c b/video/csputils.c
index bd56a1e29f..202585187a 100644
--- a/video/csputils.c
+++ b/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;
}
diff --git a/video/csputils.h b/video/csputils.h
index f2e96beb3d..9257102df5 100644
--- a/video/csputils.h
+++ b/video/csputils.h
@@ -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);
diff --git a/video/out/gl_video.c b/video/out/gl_video.c
index 989c1792db..52ad1a66f4 100644
--- a/video/out/gl_video.c
+++ b/video/out/gl_video.c
@@ -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]);
}