diff --git a/demux/demux_disc.c b/demux/demux_disc.c index 3dfff45403..8cd4b7b5e9 100644 --- a/demux/demux_disc.c +++ b/demux/demux_disc.c @@ -94,7 +94,7 @@ static void add_dvd_streams(demuxer_t *demuxer) // emulate the extradata struct mp_csp_params csp = MP_CSP_PARAMS_DEFAULTS; - struct mp_cmat cmatrix; + struct pl_transform3x3 cmatrix; mp_get_csp_matrix(&csp, &cmatrix); char *s = talloc_strdup(sh, ""); diff --git a/sub/sd_ass.c b/sub/sd_ass.c index 28ed23aecf..d191145f10 100644 --- a/sub/sd_ass.c +++ b/sub/sd_ass.c @@ -967,14 +967,14 @@ static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts) struct mp_csp_params vs_params = MP_CSP_PARAMS_DEFAULTS; vs_params.repr.sys = csp; vs_params.repr.levels = levels; - struct mp_cmat vs_yuv2rgb, vs_rgb2yuv; + struct pl_transform3x3 vs_yuv2rgb; mp_get_csp_matrix(&vs_params, &vs_yuv2rgb); - mp_invert_cmat(&vs_rgb2yuv, &vs_yuv2rgb); + pl_transform3x3_invert(&vs_yuv2rgb); // Proper conversion to RGB struct mp_csp_params rgb_params = MP_CSP_PARAMS_DEFAULTS; rgb_params.color = params.color; - struct mp_cmat vs2rgb; + struct pl_transform3x3 vs2rgb; mp_get_csp_matrix(&rgb_params, &vs2rgb); for (int n = 0; n < parts->num_parts; n++) { @@ -985,7 +985,7 @@ static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts) int b = (color >> 8u) & 0xff; int a = 0xff - (color & 0xff); int rgb[3] = {r, g, b}, yuv[3]; - mp_map_fixp_color(&vs_rgb2yuv, 8, rgb, 8, yuv); + mp_map_fixp_color(&vs_yuv2rgb, 8, rgb, 8, yuv); mp_map_fixp_color(&vs2rgb, 8, yuv, 8, rgb); sb->libass.color = MP_ASS_RGBA(rgb[0], rgb[1], rgb[2], a); } diff --git a/video/csputils.c b/video/csputils.c index 6b708f7525..3a12fda9f0 100644 --- a/video/csputils.c +++ b/video/csputils.c @@ -3,8 +3,6 @@ * * Copyleft (C) 2009 Reimar Döffinger * - * mp_invert_cmat based on DarkPlaces engine (relicensed from GPL to LGPL) - * * This file is part of mpv. * * mpv is free software; you can redistribute it and/or @@ -170,260 +168,17 @@ enum pl_color_primaries mp_csp_guess_primaries(int width, int height) } } -void mp_invert_matrix3x3(float m[3][3]) -{ - float m00 = m[0][0], m01 = m[0][1], m02 = m[0][2], - m10 = m[1][0], m11 = m[1][1], m12 = m[1][2], - m20 = m[2][0], m21 = m[2][1], m22 = m[2][2]; - - // calculate the adjoint - m[0][0] = (m11 * m22 - m21 * m12); - m[0][1] = -(m01 * m22 - m21 * m02); - m[0][2] = (m01 * m12 - m11 * m02); - m[1][0] = -(m10 * m22 - m20 * m12); - m[1][1] = (m00 * m22 - m20 * m02); - m[1][2] = -(m00 * m12 - m10 * m02); - m[2][0] = (m10 * m21 - m20 * m11); - m[2][1] = -(m00 * m21 - m20 * m01); - m[2][2] = (m00 * m11 - m10 * m01); - - // calculate the determinant (as inverse == 1/det * adjoint, - // adjoint * m == identity * det, so this calculates the det) - float det = m00 * m[0][0] + m10 * m[0][1] + m20 * m[0][2]; - det = 1.0f / det; - - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) - m[i][j] *= det; - } -} - -// A := A * B -static void mp_mul_matrix3x3(float a[3][3], float b[3][3]) -{ - float a00 = a[0][0], a01 = a[0][1], a02 = a[0][2], - a10 = a[1][0], a11 = a[1][1], a12 = a[1][2], - a20 = a[2][0], a21 = a[2][1], a22 = a[2][2]; - - for (int i = 0; i < 3; i++) { - a[0][i] = a00 * b[0][i] + a01 * b[1][i] + a02 * b[2][i]; - a[1][i] = a10 * b[0][i] + a11 * b[1][i] + a12 * b[2][i]; - a[2][i] = a20 * b[0][i] + a21 * b[1][i] + a22 * b[2][i]; - } -} - -// return the primaries associated with a certain mp_csp_primaries val -struct mp_csp_primaries mp_get_csp_primaries(enum pl_color_primaries spc) -{ - /* - Values from: ITU-R Recommendations BT.470-6, BT.601-7, BT.709-5, BT.2020-0 - - https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.470-6-199811-S!!PDF-E.pdf - https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.601-7-201103-I!!PDF-E.pdf - https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.709-5-200204-I!!PDF-E.pdf - https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2020-0-201208-I!!PDF-E.pdf - - Other colorspaces from https://en.wikipedia.org/wiki/RGB_color_space#Specifications - */ - - // CIE standard illuminant series - static const struct mp_csp_col_xy - d50 = {0.34577, 0.35850}, - d65 = {0.31271, 0.32902}, - c = {0.31006, 0.31616}, - dci = {0.31400, 0.35100}, - e = {1.0/3.0, 1.0/3.0}; - - switch (spc) { - case PL_COLOR_PRIM_BT_470M: - return (struct mp_csp_primaries) { - .red = {0.670, 0.330}, - .green = {0.210, 0.710}, - .blue = {0.140, 0.080}, - .white = c - }; - case PL_COLOR_PRIM_BT_601_525: - return (struct mp_csp_primaries) { - .red = {0.630, 0.340}, - .green = {0.310, 0.595}, - .blue = {0.155, 0.070}, - .white = d65 - }; - case PL_COLOR_PRIM_BT_601_625: - return (struct mp_csp_primaries) { - .red = {0.640, 0.330}, - .green = {0.290, 0.600}, - .blue = {0.150, 0.060}, - .white = d65 - }; - // This is the default assumption if no colorspace information could - // be determined, eg. for files which have no video channel. - case PL_COLOR_PRIM_UNKNOWN: - case PL_COLOR_PRIM_BT_709: - return (struct mp_csp_primaries) { - .red = {0.640, 0.330}, - .green = {0.300, 0.600}, - .blue = {0.150, 0.060}, - .white = d65 - }; - case PL_COLOR_PRIM_BT_2020: - return (struct mp_csp_primaries) { - .red = {0.708, 0.292}, - .green = {0.170, 0.797}, - .blue = {0.131, 0.046}, - .white = d65 - }; - case PL_COLOR_PRIM_APPLE: - return (struct mp_csp_primaries) { - .red = {0.625, 0.340}, - .green = {0.280, 0.595}, - .blue = {0.115, 0.070}, - .white = d65 - }; - case PL_COLOR_PRIM_ADOBE: - return (struct mp_csp_primaries) { - .red = {0.640, 0.330}, - .green = {0.210, 0.710}, - .blue = {0.150, 0.060}, - .white = d65 - }; - case PL_COLOR_PRIM_PRO_PHOTO: - return (struct mp_csp_primaries) { - .red = {0.7347, 0.2653}, - .green = {0.1596, 0.8404}, - .blue = {0.0366, 0.0001}, - .white = d50 - }; - case PL_COLOR_PRIM_CIE_1931: - return (struct mp_csp_primaries) { - .red = {0.7347, 0.2653}, - .green = {0.2738, 0.7174}, - .blue = {0.1666, 0.0089}, - .white = e - }; - // From SMPTE RP 431-2 and 432-1 - case PL_COLOR_PRIM_DCI_P3: - case PL_COLOR_PRIM_DISPLAY_P3: - return (struct mp_csp_primaries) { - .red = {0.680, 0.320}, - .green = {0.265, 0.690}, - .blue = {0.150, 0.060}, - .white = spc == PL_COLOR_PRIM_DCI_P3 ? dci : d65 - }; - // From Panasonic VARICAM reference manual - case PL_COLOR_PRIM_V_GAMUT: - return (struct mp_csp_primaries) { - .red = {0.730, 0.280}, - .green = {0.165, 0.840}, - .blue = {0.100, -0.03}, - .white = d65 - }; - // From Sony S-Log reference manual - case PL_COLOR_PRIM_S_GAMUT: - return (struct mp_csp_primaries) { - .red = {0.730, 0.280}, - .green = {0.140, 0.855}, - .blue = {0.100, -0.05}, - .white = d65 - }; - // from EBU Tech. 3213-E - case PL_COLOR_PRIM_EBU_3213: - return (struct mp_csp_primaries) { - .red = {0.630, 0.340}, - .green = {0.295, 0.605}, - .blue = {0.155, 0.077}, - .white = d65 - }; - // From H.273, traditional film with Illuminant C - case PL_COLOR_PRIM_FILM_C: - return (struct mp_csp_primaries) { - .red = {0.681, 0.319}, - .green = {0.243, 0.692}, - .blue = {0.145, 0.049}, - .white = c - }; - // From libplacebo source code - case PL_COLOR_PRIM_ACES_AP0: - return (struct mp_csp_primaries) { - .red = {0.7347, 0.2653}, - .green = {0.0000, 1.0000}, - .blue = {0.0001, -0.0770}, - .white = {0.32168, 0.33767}, - }; - // From libplacebo source code - case PL_COLOR_PRIM_ACES_AP1: - return (struct mp_csp_primaries) { - .red = {0.713, 0.293}, - .green = {0.165, 0.830}, - .blue = {0.128, 0.044}, - .white = {0.32168, 0.33767}, - }; - default: - return (struct mp_csp_primaries) {{0}}; - } -} - -// Get the nominal peak for a given colorspace, relative to the reference white -// level. In other words, this returns the brightest encodable value that can -// be represented by a given transfer curve. -float mp_trc_nom_peak(enum pl_color_transfer trc) -{ - switch (trc) { - case PL_COLOR_TRC_PQ: return 10000.0 / MP_REF_WHITE; - case PL_COLOR_TRC_HLG: return 12.0 / MP_REF_WHITE_HLG; - case PL_COLOR_TRC_V_LOG: return 46.0855; - case PL_COLOR_TRC_S_LOG1: return 6.52; - case PL_COLOR_TRC_S_LOG2: return 9.212; - } - - return 1.0; -} - -bool mp_trc_is_hdr(enum pl_color_transfer trc) -{ - return mp_trc_nom_peak(trc) > 1.0; -} - -// Compute the RGB/XYZ matrix as described here: -// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html -void mp_get_rgb2xyz_matrix(struct mp_csp_primaries space, float m[3][3]) -{ - float S[3], X[4], Z[4]; - - // Convert from CIE xyY to XYZ. Note that Y=1 holds true for all primaries - X[0] = space.red.x / space.red.y; - X[1] = space.green.x / space.green.y; - X[2] = space.blue.x / space.blue.y; - X[3] = space.white.x / space.white.y; - - Z[0] = (1 - space.red.x - space.red.y) / space.red.y; - Z[1] = (1 - space.green.x - space.green.y) / space.green.y; - Z[2] = (1 - space.blue.x - space.blue.y) / space.blue.y; - Z[3] = (1 - space.white.x - space.white.y) / space.white.y; - - // S = XYZ^-1 * W - for (int i = 0; i < 3; i++) { - m[0][i] = X[i]; - m[1][i] = 1; - m[2][i] = Z[i]; - } - - mp_invert_matrix3x3(m); - - for (int i = 0; i < 3; i++) - S[i] = m[i][0] * X[3] + m[i][1] * 1 + m[i][2] * Z[3]; - - // M = [Sc * XYZc] - for (int i = 0; i < 3; i++) { - m[0][i] = S[i] * X[i]; - m[1][i] = S[i] * 1; - m[2][i] = S[i] * Z[i]; - } -} +// LMS<-XYZ revised matrix from CIECAM97, based on a linear transform and +// normalized for equal energy on monochrome inputs +static const pl_matrix3x3 m_cat97 = {{ + { 0.8562, 0.3372, -0.1934 }, + { -0.8360, 1.8327, 0.0033 }, + { 0.0357, -0.0469, 1.0112 }, +}}; // M := M * XYZd<-XYZs -static void mp_apply_chromatic_adaptation(struct mp_csp_col_xy src, - struct mp_csp_col_xy dest, float m[3][3]) +static void apply_chromatic_adaptation(struct pl_cie_xy src, + struct pl_cie_xy dest, pl_matrix3x3 *mat) { // If the white points are nearly identical, this is a wasteful identity // operation. @@ -432,98 +187,33 @@ static void mp_apply_chromatic_adaptation(struct mp_csp_col_xy src, // 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 Ma, we use the CIECAM97 revised (linear) matrix + float C[3][2]; for (int i = 0; i < 3; i++) { // source cone - C[i][0] = bradford[i][0] * mp_xy_X(src) - + bradford[i][1] * 1 - + bradford[i][2] * mp_xy_Z(src); + C[i][0] = m_cat97.m[i][0] * pl_cie_X(src) + + m_cat97.m[i][1] * 1 + + m_cat97.m[i][2] * pl_cie_Z(src); // dest cone - C[i][1] = bradford[i][0] * mp_xy_X(dest) - + bradford[i][1] * 1 - + bradford[i][2] * mp_xy_Z(dest); + C[i][1] = m_cat97.m[i][0] * pl_cie_X(dest) + + m_cat97.m[i][1] * 1 + + m_cat97.m[i][2] * pl_cie_Z(dest); } // tmp := I * [Cd/Cs] * Ma + pl_matrix3x3 tmp = {0}; for (int i = 0; i < 3; i++) - tmp[i][i] = C[i][1] / C[i][0]; + tmp.m[i][i] = C[i][1] / C[i][0]; - mp_mul_matrix3x3(tmp, bradford); + pl_matrix3x3_mul(&tmp, &m_cat97); // M := M * Ma^-1 * tmp - mp_invert_matrix3x3(bradford); - mp_mul_matrix3x3(m, bradford); - mp_mul_matrix3x3(m, tmp); -} - -// get the coefficients of the source -> dest cms matrix -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 - // 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, 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); - mp_mul_matrix3x3(m, tmp); -} - -// get the coefficients of an ST 428-1 xyz -> rgb conversion matrix -// intent = the rendering intent used to convert to the target primaries -static void mp_get_xyz2rgb_coeffs(struct mp_csp_params *params, - enum mp_render_intent intent, struct mp_cmat *m) -{ - // Convert to DCI-P3 - struct mp_csp_primaries prim = mp_get_csp_primaries(PL_COLOR_PRIM_DCI_P3); - float brightness = params->brightness; - mp_get_rgb2xyz_matrix(prim, m->m); - mp_invert_matrix3x3(m->m); - - // All non-absolute mappings want to map source white to target white - if (intent != MP_INTENT_ABSOLUTE_COLORIMETRIC) { - // SMPTE EG 432-1 Annex H defines the white point as equal energy - static const struct mp_csp_col_xy smpte432 = {1.0/3.0, 1.0/3.0}; - mp_apply_chromatic_adaptation(smpte432, prim.white, m->m); - } - - // 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. - // As this is an aesthetic setting only, any exact values do not matter. - brightness *= fabs(brightness); - - for (int i = 0; i < 3; i++) - m->c[i] = brightness; + pl_matrix3x3 ma_inv = m_cat97; + pl_matrix3x3_invert(&ma_inv); + pl_matrix3x3_mul(mat, &ma_inv); + pl_matrix3x3_mul(mat, &tmp); } // Get multiplication factor required if image data is fit within the LSBs of a @@ -608,19 +298,19 @@ void mp_get_csp_uint_mul(enum pl_color_system csp, enum pl_color_levels levels, * Under these conditions the given parameters lr, lg, lb uniquely * determine the mapping of Y, U, V to R, G, B. */ -static void luma_coeffs(struct mp_cmat *mat, float lr, float lg, float lb) +static void luma_coeffs(struct pl_transform3x3 *mat, float lr, float lg, float lb) { assert(fabs(lr+lg+lb - 1) < 1e-6); - *mat = (struct mp_cmat) { - { {1, 0, 2 * (1-lr) }, - {1, -2 * (1-lb) * lb/lg, -2 * (1-lr) * lr/lg }, - {1, 2 * (1-lb), 0 } }, + *mat = (struct pl_transform3x3) { + { {{1, 0, 2 * (1-lr) }, + {1, -2 * (1-lb) * lb/lg, -2 * (1-lr) * lr/lg }, + {1, 2 * (1-lb), 0 }} }, // Constant coefficients (mat->c) not set here }; } // get the coefficients of the yuv -> rgb conversion matrix -void mp_get_csp_matrix(struct mp_csp_params *params, struct mp_cmat *m) +void mp_get_csp_matrix(struct mp_csp_params *params, struct pl_transform3x3 *m) { enum pl_color_system colorspace = params->repr.sys; if (colorspace <= PL_COLOR_SYSTEM_UNKNOWN || colorspace >= PL_COLOR_SYSTEM_COUNT) @@ -639,29 +329,30 @@ void mp_get_csp_matrix(struct mp_csp_params *params, struct mp_cmat *m) // If this clips on any VO, a constant 0.5 coefficient can be added // to the chroma channels to normalize them into [0,1]. This is not // currently needed by anything, though. - *m = (struct mp_cmat){{{0, 0, 1}, {1, 0, 0}, {0, 1, 0}}}; + *m = (struct pl_transform3x3){{{{0, 0, 1}, {1, 0, 0}, {0, 1, 0}}}}; break; } case PL_COLOR_SYSTEM_RGB: { - *m = (struct mp_cmat){{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}}; + *m = (struct pl_transform3x3){{{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}}}; levels_in = -1; break; } case PL_COLOR_SYSTEM_XYZ: { - // The vo should probably not be using a matrix generated by this - // function for XYZ sources, but if it does, let's just convert it to - // an equivalent RGB space based on the colorimetry metadata it - // provided in mp_csp_params. (At the risk of clipping, if the - // chosen primaries are too small to fit the actual data) - mp_get_xyz2rgb_coeffs(params, MP_INTENT_RELATIVE_COLORIMETRIC, m); + // For lack of anything saner to do, just assume the caller wants + // DCI-P3 primaries, which is a reasonable assumption. + const struct pl_raw_primaries *dst = pl_raw_primaries_get(PL_COLOR_PRIM_DCI_P3); + pl_matrix3x3 mat = pl_get_xyz2rgb_matrix(dst); + // DCDM X'Y'Z' is expected to have equal energy white point (EG 432-1 Annex H) + apply_chromatic_adaptation((struct pl_cie_xy){1.0/3.0, 1.0/3.0}, dst->white, &mat); + *m = (struct pl_transform3x3) { .mat = mat }; levels_in = -1; break; } case PL_COLOR_SYSTEM_YCGCO: { - *m = (struct mp_cmat) { - {{1, -1, 1}, - {1, 1, 0}, - {1, -1, -1}}, + *m = (struct pl_transform3x3) { + {{{1, -1, 1}, + {1, 1, 0}, + {1, -1, -1}}}, }; break; } @@ -680,9 +371,9 @@ void mp_get_csp_matrix(struct mp_csp_params *params, struct mp_cmat *m) float huecos = params->gray ? 0 : params->saturation * cos(params->hue); float huesin = params->gray ? 0 : params->saturation * sin(params->hue); for (int i = 0; i < 3; i++) { - float u = m->m[i][1], v = m->m[i][2]; - m->m[i][1] = huecos * u - huesin * v; - m->m[i][2] = huesin * u + huecos * v; + float u = m->mat.m[i][1], v = m->mat.m[i][2]; + m->mat.m[i][1] = huecos * u - huesin * v; + m->mat.m[i][2] = huesin * u + huecos * v; } } @@ -728,13 +419,13 @@ void mp_get_csp_matrix(struct mp_csp_params *params, struct mp_cmat *m) cmul *= params->contrast; for (int i = 0; i < 3; i++) { - m->m[i][0] *= ymul; - m->m[i][1] *= cmul; - m->m[i][2] *= cmul; + m->mat.m[i][0] *= ymul; + m->mat.m[i][1] *= cmul; + m->mat.m[i][2] *= cmul; // Set c so that Y=umin,UV=cmid maps to RGB=min (black to black), // also add brightness offset (black lift) - m->c[i] = rgblev.min - m->m[i][0] * yuvlev.ymin - - (m->m[i][1] + m->m[i][2]) * yuvlev.cmid + m->c[i] = rgblev.min - m->mat.m[i][0] * yuvlev.ymin + - (m->mat.m[i][1] + m->mat.m[i][2]) * yuvlev.cmid + params->brightness; } } @@ -822,32 +513,17 @@ void mp_csp_equalizer_state_get(struct mp_csp_equalizer_state *state, mp_csp_copy_equalizer_values(params, opts); } -void mp_invert_cmat(struct mp_cmat *out, struct mp_cmat *in) -{ - *out = *in; - mp_invert_matrix3x3(out->m); - - // fix the constant coefficient - // rgb = M * yuv + C - // M^-1 * rgb = yuv + M^-1 * C - // yuv = M^-1 * rgb - M^-1 * C - // ^^^^^^^^^^ - out->c[0] = -(out->m[0][0] * in->c[0] + out->m[0][1] * in->c[1] + out->m[0][2] * in->c[2]); - out->c[1] = -(out->m[1][0] * in->c[0] + out->m[1][1] * in->c[1] + out->m[1][2] * in->c[2]); - out->c[2] = -(out->m[2][0] * in->c[0] + out->m[2][1] * in->c[1] + out->m[2][2] * in->c[2]); -} - // Multiply the color in c with the given matrix. // i/o is {R, G, B} or {Y, U, V} (depending on input/output and matrix), using // a fixed point representation with the given number of bits (so for bits==8, // [0,255] maps to [0,1]). The output is clipped to the range as needed. -void mp_map_fixp_color(struct mp_cmat *matrix, int ibits, int in[3], +void mp_map_fixp_color(struct pl_transform3x3 *matrix, int ibits, int in[3], int obits, int out[3]) { for (int i = 0; i < 3; i++) { double val = matrix->c[i]; for (int x = 0; x < 3; x++) - val += matrix->m[i][x] * in[x] / ((1 << ibits) - 1); + val += matrix->mat.m[i][x] * in[x] / ((1 << ibits) - 1); int ival = lrint(val * ((1 << obits) - 1)); out[i] = av_clip(ival, 0, (1 << obits) - 1); } diff --git a/video/csputils.h b/video/csputils.h index 9b2d0378df..5ab1287d9c 100644 --- a/video/csputils.h +++ b/video/csputils.h @@ -46,15 +46,6 @@ enum mp_csp_light { extern const struct m_opt_choice_alternatives mp_csp_light_names[]; -// 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 -}; - // The numeric values (except -1) match the Matroska StereoMode element value. enum mp_stereo3d_mode { MP_STEREO3D_INVALID = -1, @@ -123,59 +114,15 @@ bool mp_csp_equalizer_state_changed(struct mp_csp_equalizer_state *state); void mp_csp_equalizer_state_get(struct mp_csp_equalizer_state *state, struct mp_csp_params *params); -struct mp_csp_col_xy { - float x, y; -}; - -static inline float mp_xy_X(struct mp_csp_col_xy xy) { - return xy.x / xy.y; -} - -static inline float mp_xy_Z(struct mp_csp_col_xy xy) { - return (1 - xy.x - xy.y) / xy.y; -} - -struct mp_csp_primaries { - struct mp_csp_col_xy red, green, blue, white; -}; - enum pl_color_system mp_csp_guess_colorspace(int width, int height); enum pl_color_primaries mp_csp_guess_primaries(int width, int height); -struct mp_csp_primaries mp_get_csp_primaries(enum pl_color_primaries csp); -float mp_trc_nom_peak(enum pl_color_transfer trc); -bool mp_trc_is_hdr(enum pl_color_transfer trc); - -/* Color conversion matrix: RGB = m * YUV + c - * m is in row-major matrix, with m[row][col], e.g.: - * [ a11 a12 a13 ] float m[3][3] = { { a11, a12, a13 }, - * [ a21 a22 a23 ] { a21, a22, a23 }, - * [ a31 a32 a33 ] { a31, a32, a33 } }; - * This is accessed as e.g.: m[2-1][1-1] = a21 - * In particular, each row contains all the coefficients for one of R, G, B, - * while each column contains all the coefficients for one of Y, U, V: - * m[r,g,b][y,u,v] = ... - * The matrix could also be viewed as group of 3 vectors, e.g. the 1st column - * is the Y vector (1, 1, 1), the 2nd is the U vector, the 3rd the V vector. - * The matrix might also be used for other conversions and colorspaces. - */ -struct mp_cmat { - float m[3][3]; - float c[3]; -}; - -void mp_get_rgb2xyz_matrix(struct mp_csp_primaries space, 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]); - double mp_get_csp_mul(enum pl_color_system csp, int input_bits, int texture_bits); void mp_get_csp_uint_mul(enum pl_color_system csp, enum pl_color_levels levels, int bits, int component, double *out_m, double *out_o); -void mp_get_csp_matrix(struct mp_csp_params *params, struct mp_cmat *out); +void mp_get_csp_matrix(struct mp_csp_params *params, struct pl_transform3x3 *out); -void mp_invert_matrix3x3(float m[3][3]); -void mp_invert_cmat(struct mp_cmat *out, struct mp_cmat *in); -void mp_map_fixp_color(struct mp_cmat *matrix, int ibits, int in[3], +void mp_map_fixp_color(struct pl_transform3x3 *matrix, int ibits, int in[3], int obits, int out[3]); #endif /* MPLAYER_CSPUTILS_H */ diff --git a/video/mp_image.c b/video/mp_image.c index b86ecb9b6a..e8e1690463 100644 --- a/video/mp_image.c +++ b/video/mp_image.c @@ -961,11 +961,11 @@ void mp_image_params_guess_csp(struct mp_image_params *params) } else { // If the signal peak is unknown, we're forced to pick the TRC's // nominal range as the signal peak to prevent clipping - params->color.hdr.max_luma = mp_trc_nom_peak(params->color.transfer) * MP_REF_WHITE; + params->color.hdr.max_luma = pl_color_transfer_nominal_peak(params->color.transfer) * MP_REF_WHITE; } } - if (!mp_trc_is_hdr(params->color.transfer)) { + if (!pl_color_space_is_hdr(¶ms->color)) { // Some clips have leftover HDR metadata after conversion to SDR, so to // avoid blowing up the tone mapping code, strip/sanitize it params->color.hdr = pl_hdr_metadata_empty; diff --git a/video/out/gpu/lcms.c b/video/out/gpu/lcms.c index 00d819d2f0..1e451aa7a2 100644 --- a/video/out/gpu/lcms.c +++ b/video/out/gpu/lcms.c @@ -197,12 +197,12 @@ static cmsHPROFILE get_vid_profile(struct gl_lcms *p, cmsContext cms, // The input profile for the transformation is dependent on the video // primaries and transfer characteristics - struct mp_csp_primaries csp = mp_get_csp_primaries(prim); - cmsCIExyY wp_xyY = {csp.white.x, csp.white.y, 1.0}; + const struct pl_raw_primaries *csp = pl_raw_primaries_get(prim); + cmsCIExyY wp_xyY = {csp->white.x, csp->white.y, 1.0}; cmsCIExyYTRIPLE prim_xyY = { - .Red = {csp.red.x, csp.red.y, 1.0}, - .Green = {csp.green.x, csp.green.y, 1.0}, - .Blue = {csp.blue.x, csp.blue.y, 1.0}, + .Red = {csp->red.x, csp->red.y, 1.0}, + .Green = {csp->green.x, csp->green.y, 1.0}, + .Blue = {csp->blue.x, csp->blue.y, 1.0}, }; cmsToneCurve *tonecurve[3] = {0}; @@ -242,7 +242,7 @@ static cmsHPROFILE get_vid_profile(struct gl_lcms *p, cmsContext cms, // function. Relative colorimetric is used since we want to // approximate the BT.1886 to the target device's actual black // point even in e.g. perceptual mode - const int intent = MP_INTENT_RELATIVE_COLORIMETRIC; + const int intent = PL_INTENT_RELATIVE_COLORIMETRIC; cmsCIEXYZ bp_XYZ; if (!cmsDetectBlackPoint(&bp_XYZ, disp_profile, intent, 0)) return false; @@ -519,7 +519,7 @@ const struct m_sub_options mp_icc_conf = { .size = sizeof(struct mp_icc_opts), .defaults = &(const struct mp_icc_opts) { .size_str = "auto", - .intent = MP_INTENT_RELATIVE_COLORIMETRIC, + .intent = PL_INTENT_RELATIVE_COLORIMETRIC, .use_embedded = true, .cache = true, }, diff --git a/video/out/gpu/video.c b/video/out/gpu/video.c index 5e0175366a..fcde3cd5c9 100644 --- a/video/out/gpu/video.c +++ b/video/out/gpu/video.c @@ -2344,9 +2344,9 @@ static void pass_convert_yuv(struct gl_video *p) // Conversion to RGB. For RGB itself, this still applies e.g. brightness // and contrast controls, or expansion of e.g. LSB-packed 10 bit data. - struct mp_cmat m = {{{0}}}; + struct pl_transform3x3 m = {0}; mp_get_csp_matrix(&cparams, &m); - gl_sc_uniform_mat3(sc, "colormatrix", true, &m.m[0][0]); + gl_sc_uniform_mat3(sc, "colormatrix", true, &m.mat.m[0][0]); gl_sc_uniform_vec3(sc, "colormatrix_c", m.c); GLSL(color.rgb = mat3(colormatrix) * color.rgb + colormatrix_c;) @@ -2482,7 +2482,7 @@ static void pass_scale_main(struct gl_video *p) // Linear light downscaling results in nasty artifacts for HDR curves // due to the potentially extreme brightness differences severely // compounding any ringing. So just scale in gamma light instead. - if (mp_trc_is_hdr(p->image_params.color.transfer)) + if (pl_color_space_is_hdr(&p->image_params.color)) use_linear = false; } else if (upscaling) { use_linear = p->opts.linear_upscaling || p->opts.sigmoid_upscaling; @@ -2592,7 +2592,7 @@ static void pass_colormanage(struct gl_video *p, struct pl_color_space src, // limitation reasons, so we use a gamma 2.2 input curve here instead. // We could pick any value we want here, the difference is just coding // efficiency. - if (mp_trc_is_hdr(trc_orig)) + if (pl_color_space_is_hdr(&p->image_params.color)) trc_orig = PL_COLOR_TRC_GAMMA22; if (gl_video_get_lut3d(p, prim_orig, trc_orig)) { @@ -2629,7 +2629,7 @@ static void pass_colormanage(struct gl_video *p, struct pl_color_space src, // Avoid outputting linear light or HDR content "by default". For these // just pick gamma 2.2 as a default, since it's a good estimate for // the response of typical displays - if (dst.transfer == PL_COLOR_TRC_LINEAR || mp_trc_is_hdr(dst.transfer)) + if (dst.transfer == PL_COLOR_TRC_LINEAR || pl_color_space_is_hdr(&dst)) dst.transfer = PL_COLOR_TRC_GAMMA22; } @@ -2637,9 +2637,9 @@ static void pass_colormanage(struct gl_video *p, struct pl_color_space src, // it from the chosen transfer function. Also normalize the src peak, in // case it was unknown if (!dst.hdr.max_luma) - dst.hdr.max_luma = mp_trc_nom_peak(dst.transfer) * MP_REF_WHITE; + dst.hdr.max_luma = pl_color_transfer_nominal_peak(dst.transfer) * MP_REF_WHITE; if (!src.hdr.max_luma) - src.hdr.max_luma = mp_trc_nom_peak(src.transfer) * MP_REF_WHITE; + src.hdr.max_luma = pl_color_transfer_nominal_peak(src.transfer) * MP_REF_WHITE; // Whitelist supported modes switch (p->opts.tone_map.curve) { @@ -2671,7 +2671,7 @@ static void pass_colormanage(struct gl_video *p, struct pl_color_space src, } struct gl_tone_map_opts tone_map = p->opts.tone_map; - bool detect_peak = tone_map.compute_peak >= 0 && mp_trc_is_hdr(src.transfer) + bool detect_peak = tone_map.compute_peak >= 0 && pl_color_space_is_hdr(&src) && src.hdr.max_luma > dst.hdr.max_luma; if (detect_peak && !p->hdr_peak_ssbo) { diff --git a/video/out/gpu/video_shaders.c b/video/out/gpu/video_shaders.c index 2201e12d46..62fe11caae 100644 --- a/video/out/gpu/video_shaders.c +++ b/video/out/gpu/video_shaders.c @@ -17,6 +17,8 @@ #include +#include + #include "video_shaders.h" #include "video.h" @@ -337,7 +339,7 @@ static const float SLOG_A = 0.432699, // // These functions always output to a normalized scale of [0,1], for // convenience of the video.c code that calls it. To get the values in an -// absolute scale, multiply the result by `mp_trc_nom_peak(trc)` +// absolute scale, multiply the result by `pl_color_transfer_nominal_peak(trc)` void pass_linearize(struct gl_shader_cache *sc, enum pl_color_transfer trc) { if (trc == PL_COLOR_TRC_LINEAR) @@ -430,7 +432,7 @@ void pass_linearize(struct gl_shader_cache *sc, enum pl_color_transfer trc) } // Rescale to prevent clipping on non-float textures - GLSLF("color.rgb *= vec3(1.0/%f);\n", mp_trc_nom_peak(trc)); + GLSLF("color.rgb *= vec3(1.0/%f);\n", pl_color_transfer_nominal_peak(trc)); } // Delinearize (compress), given a TRC as output. This corresponds to the @@ -445,7 +447,7 @@ void pass_delinearize(struct gl_shader_cache *sc, enum pl_color_transfer trc) GLSLF("// delinearize\n"); GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);) - GLSLF("color.rgb *= vec3(%f);\n", mp_trc_nom_peak(trc)); + GLSLF("color.rgb *= vec3(%f);\n", pl_color_transfer_nominal_peak(trc)); switch (trc) { case PL_COLOR_TRC_SRGB: @@ -842,11 +844,10 @@ void pass_color_map(struct gl_shader_cache *sc, bool is_linear, // Some operations need access to the video's luma coefficients, so make // them available - float rgb2xyz[3][3]; - mp_get_rgb2xyz_matrix(mp_get_csp_primaries(src.primaries), rgb2xyz); - gl_sc_uniform_vec3(sc, "src_luma", rgb2xyz[1]); - mp_get_rgb2xyz_matrix(mp_get_csp_primaries(dst.primaries), rgb2xyz); - gl_sc_uniform_vec3(sc, "dst_luma", rgb2xyz[1]); + pl_matrix3x3 rgb2xyz = pl_get_rgb2xyz_matrix(pl_raw_primaries_get(src.primaries)); + gl_sc_uniform_vec3(sc, "src_luma", rgb2xyz.m[1]); + rgb2xyz = pl_get_rgb2xyz_matrix(pl_raw_primaries_get(dst.primaries)); + gl_sc_uniform_vec3(sc, "dst_luma", rgb2xyz.m[1]); bool need_ootf = src_light != dst_light; if (src_light == MP_CSP_LIGHT_SCENE_HLG && src.hdr.max_luma != dst.hdr.max_luma) @@ -867,7 +868,7 @@ void pass_color_map(struct gl_shader_cache *sc, bool is_linear, } // Pre-scale the incoming values into an absolute scale - GLSLF("color.rgb *= vec3(%f);\n", mp_trc_nom_peak(src.transfer)); + GLSLF("color.rgb *= vec3(%f);\n", pl_color_transfer_nominal_peak(src.transfer)); if (need_ootf) pass_ootf(sc, src_light, src.hdr.max_luma / MP_REF_WHITE); @@ -880,11 +881,11 @@ void pass_color_map(struct gl_shader_cache *sc, bool is_linear, // Adapt to the right colorspace if necessary if (src.primaries != dst.primaries) { - struct mp_csp_primaries csp_src = mp_get_csp_primaries(src.primaries), - csp_dst = mp_get_csp_primaries(dst.primaries); - float m[3][3] = {{0}}; - mp_get_cms_matrix(csp_src, csp_dst, MP_INTENT_RELATIVE_COLORIMETRIC, m); - gl_sc_uniform_mat3(sc, "cms_matrix", true, &m[0][0]); + const struct pl_raw_primaries *csp_src = pl_raw_primaries_get(src.primaries), + *csp_dst = pl_raw_primaries_get(dst.primaries); + pl_matrix3x3 m = pl_get_color_mapping_matrix(csp_src, csp_dst, + PL_INTENT_RELATIVE_COLORIMETRIC); + gl_sc_uniform_mat3(sc, "cms_matrix", true, &m.m[0][0]); GLSL(color.rgb = cms_matrix * color.rgb;) if (!opts->gamut_mode || opts->gamut_mode == GAMUT_DESATURATE) { @@ -907,8 +908,8 @@ void pass_color_map(struct gl_shader_cache *sc, bool is_linear, // For SDR, we normalize to the chosen signal peak. For HDR, we normalize // to the encoding range of the transfer function. float dst_range = dst.hdr.max_luma / MP_REF_WHITE; - if (mp_trc_is_hdr(dst.transfer)) - dst_range = mp_trc_nom_peak(dst.transfer); + if (pl_color_space_is_hdr(&dst)) + dst_range = pl_color_transfer_nominal_peak(dst.transfer); GLSLF("color.rgb *= vec3(%f);\n", 1.0 / dst_range); @@ -1009,7 +1010,7 @@ void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts, GLSL(noise.z = rand(h); h = permute(h);) // Noise is scaled to the signal level to prevent extreme noise for HDR - float gain = opts->grain/8192.0 / mp_trc_nom_peak(trc); + float gain = opts->grain/8192.0 / pl_color_transfer_nominal_peak(trc); GLSLF("color.xyz += %f * (noise - vec3(0.5));\n", gain); GLSLF("}\n"); } diff --git a/video/vdpau_mixer.c b/video/vdpau_mixer.c index b1aed70e78..5adb3b4ec3 100644 --- a/video/vdpau_mixer.c +++ b/video/vdpau_mixer.c @@ -193,7 +193,7 @@ static int create_vdp_mixer(struct mp_vdpau_mixer *mixer, if (!opts->chroma_deint) SET_VIDEO_ATTR(SKIP_CHROMA_DEINTERLACE, uint8_t, 1); - struct mp_cmat yuv2rgb; + struct pl_transform3x3 yuv2rgb; VdpCSCMatrix matrix; struct mp_csp_params cparams = MP_CSP_PARAMS_DEFAULTS; @@ -204,7 +204,7 @@ static int create_vdp_mixer(struct mp_vdpau_mixer *mixer, for (int r = 0; r < 3; r++) { for (int c = 0; c < 3; c++) - matrix[r][c] = yuv2rgb.m[r][c]; + matrix[r][c] = yuv2rgb.mat.m[r][c]; matrix[r][3] = yuv2rgb.c[r]; } diff --git a/video/zimg.c b/video/zimg.c index 35a987dfea..907e81deb7 100644 --- a/video/zimg.c +++ b/video/zimg.c @@ -551,7 +551,7 @@ static bool mp_zimg_state_init(struct mp_zimg_context *ctx, params.allow_approximate_gamma = 1; // leave at default for SDR, which means 100 cd/m^2 for zimg - if (ctx->dst.color.hdr.max_luma > 0 && mp_trc_is_hdr(ctx->dst.color.transfer)) + if (ctx->dst.color.hdr.max_luma > 0 && pl_color_space_is_hdr(&ctx->dst.color)) params.nominal_peak_luminance = ctx->dst.color.hdr.max_luma; st->graph = zimg_filter_graph_build(&src_fmt, &dst_fmt, ¶ms);