blob: 4ae443a162a59b4c9ff47272d5407cfac0ba7d92 [file] [log] [blame]
/*
* Copyright 2025 Advanced Micro Devices, Inc.
* SPDX-License-Identifier: MIT
*
*----------------------------------------------------------------------
* File Name : cs_funcs.c
* Purpose : Color Space functions
* Author : Vladimir Lachine (vlachine@amd.com)
* Date : September 20, 2023
* Version : 1.4
*----------------------------------------------------------------------
*
*/
#ifndef GM_SIM
#pragma code_seg("PAGED3PC")
#pragma data_seg("PAGED3PD")
#pragma const_seg("PAGED3PR")
#endif
#include "cs_funcs.h"
static const MATFLOAT cs_vec_gamma[EGT_CUSTOM][4] = {
/* c1 c2 c3 c4 */
{1.0000, 1.00, 0.00, 0.000}, /* linear */
{1.0990, 0.45, 4.50, 0.018}, /* 709 (SD/HD) */
{1.0000, 1.0 / 2.1992, 0.0, 0.0}, /* Adobe RGB 1998 */
{1.0000, 1.0 / 2.6, 0.0, 0.0}, /* DCI-P3 (SMPTE-231-2) */
{1.0000, 1.0 / 1.8, 0.0, 0.0}, /* Apple Trinitron */
{1.0550, 1.0 / 2.4, 12.92, 0.0031308}, /* sRGB */
{0.0000, 0.0, 0.0, 0.0}, /* PQ */
{0.5000, 0.0, 0.0, 0.0}, /* HLG */
{1.0000, 1.0 / 2.2, 0.0, 0.0}, /* Gamma 2.2 */
{1.0000, 1.0 / 2.4, 0.0, 0.0} /* Gamma 2.4 */
};
static const MATFLOAT cs_vec_color_space[ECST_CUSTOM][8] = {
/* Red (x, y), Green (x,y), Blue (x,y), White (x,y) */
{0.6400, 0.3300, 0.3000, 0.6000, 0.1500, 0.0600, 0.312710, 0.329020}, /* ITU_R BT.709-5/sRGB (HDTV) */
{0.6300, 0.3400, 0.3100, 0.5950, 0.1550, 0.0700, 0.312710, 0.329020}, /* SMPTE RP 145 (SDTV) */
{0.6400, 0.3300, 0.2100, 0.7100, 0.1500, 0.0600, 0.312710, 0.329020}, /* Adobe RGB (1998) */
{0.6800, 0.3200, 0.2650, 0.6900, 0.1500, 0.0600, 0.312710, 0.329020}, /* DCI P3 (SMPTE-231-2) P3D65 */
/* {0.6800, 0.3200, 0.2650, 0.6900, 0.1500, 0.0600, 0.314000, 0.351000}, // DCI P3 (SMPTE-231-2) P3D60 */
/* {0.6800, 0.3200, 0.2650, 0.6900, 0.1500, 0.0600, 0.314000, 0.351000}, // DCI P3 (SMPTE-231-2) P3DCI */
{0.6250, 0.3400, 0.2800, 0.5950, 0.1550, 0.0700, 0.312710, 0.329020}, /* Apple */
{0.6400, 0.3300, 0.2900, 0.6000, 0.1500, 0.0600, 0.312710, 0.329020}, /* EBU 3213/ITU (PAL/SEQAM) */
{0.6700, 0.3300, 0.2100, 0.7100, 0.1400, 0.0800, 0.310100, 0.316200}, /* NTSC 1953 */
{0.7350, 0.2650, 0.2740, 0.7170, 0.1660, 0.0090, 0.333300, 0.333300}, /* CIE RGB */
{0.7080, 0.2920, 0.1700, 0.7970, 0.1310, 0.0460, 0.312710, 0.329020} /* BT.2020 */
};
static MATFLOAT cs_vec_white_point[EWPT_NUM][3] = {
/* x, y, z */
{1.000000, 1.000000, 1.000000}, /* NONE */
{0.447570, 0.407440, 0.144990}, /* A - Tungsten or Incandescent, 2856K */
{0.348400, 0.351600, 0.300000}, /* B - Direct Sunlight at Noon, 4874K (obsolete) */
{0.310060, 0.316150, 0.373790}, /* C - North Sky Daylight, 6774K */
{0.345670, 0.358500, 0.295830}, /* D50 - Daylight, used for COlor Rendering, 500K */
{0.332420, 0.347430, 0.320150}, /* D55 - Daylight, used for Photograph, 5500K */
{0.312710, 0.329020, 0.358270}, /* D65 - New version of North Sky Daylight, 6504K */
{0.299020, 0.314850, 0.386130}, /* D75 - Daylight, 7500K */
{0.284800, 0.293200, 0.422000}, /* 9300K - High eff. blue phosphor monitors, 9300K */
{0.333330, 0.333330, 0.333340}, /* E - Uniform energy illuminant, 5400K */
{0.372070, 0.375120, 0.252810}, /* F2 - Cool White Fluorescent (CWF), 4200K */
{0.312850, 0.329180, 0.357970}, /* F7 - Broad-band Daylight Fluorescent, 6500K */
{0.380540, 0.376910, 0.242540}, /* F11 - Narrow-band White Fluorescent, 4000K */
{0.314000, 0.351000, 0.335000}, /* DCI-P3 */
{0.277400, 0.283600, 0.438660} /* 11000K - blue sky, 11000K */
};
static const MATFLOAT cs_vec_cct_xy[2 * CS_CCT_SIZE] = {
0.652750, 0.344462, 0.638755, 0.356498, 0.625043, 0.367454, 0.611630, 0.377232, 0.598520, 0.385788, /* 1000 */
0.585716, 0.393121, 0.573228, 0.399264, 0.561066, 0.404274, 0.549243, 0.408225, 0.537776, 0.411202,
0.526676, 0.413297, 0.515956, 0.414601, 0.505624, 0.415207, 0.495685, 0.415201, 0.486142, 0.414665, /* 2000 */
0.476993, 0.413675, 0.468234, 0.412299, 0.459857, 0.410598, 0.451855, 0.408629, 0.444216, 0.406440,
0.436929, 0.404073, 0.429981, 0.401566, 0.423358, 0.398951, 0.417046, 0.396255, 0.411032, 0.393503, /* 3000 */
0.405302, 0.390715, 0.399841, 0.387907, 0.394638, 0.385095, 0.389677, 0.382291, 0.384948, 0.379505,
0.380438, 0.376746, 0.376135, 0.374019, 0.372029, 0.371332, 0.368108, 0.368687, 0.364364, 0.366090, /* 4000 */
0.360786, 0.363543, 0.357366, 0.361048, 0.354095, 0.358605, 0.350965, 0.356217, 0.347969, 0.353884,
0.345100, 0.351607, 0.342350, 0.349384, 0.339715, 0.347215, 0.337187, 0.345102, 0.334761, 0.343041, /* 5000 */
0.332433, 0.341034, 0.330196, 0.339078, 0.328047, 0.337173, 0.325981, 0.335317, 0.323994, 0.333511,
0.322082, 0.331752, 0.320241, 0.330039, 0.318468, 0.328371, 0.316760, 0.326747, 0.315113, 0.325166, /* 6000 */
0.313524, 0.323626, 0.311992, 0.322127, 0.310513, 0.320667, 0.309085, 0.319245, 0.307705, 0.317860,
0.306372, 0.316511, 0.305083, 0.315196, 0.303837, 0.313915, 0.302631, 0.312667, 0.301463, 0.311450, /* 7000 */
0.300333, 0.310264, 0.299238, 0.309108, 0.298178, 0.307981, 0.297149, 0.306881, 0.296153, 0.305809,
0.295186, 0.304763, 0.294247, 0.303743, 0.293337, 0.302747, 0.292453, 0.301775, 0.291594, 0.300826, /* 8000 */
0.290760, 0.299899, 0.289949, 0.298995, 0.289161, 0.298111, 0.288395, 0.297248, 0.287649, 0.296405,
0.286924, 0.295581, 0.286218, 0.294776, 0.285531, 0.293989, 0.284862, 0.293220, 0.284211, 0.292467, /* 9000 */
0.283576, 0.291732, 0.282957, 0.291012, 0.282354, 0.290308, 0.281765, 0.289619, 0.281192, 0.288945,
0.280632, 0.288286, 0.280086, 0.287640, 0.279553, 0.287007, 0.279033, 0.286388, 0.278525, 0.285782, /* 10000 */
0.278029, 0.285188, 0.277544, 0.284606, 0.277071, 0.284036, 0.276608, 0.283477, 0.276156, 0.282930,
0.275714, 0.282393, 0.275281, 0.281867, 0.274858, 0.281351, 0.274444, 0.280845, 0.274039, 0.280349, /* 11000 */
0.273643, 0.279862, 0.273255, 0.279384, 0.272875, 0.278915, 0.272503, 0.278455, 0.272139, 0.278004,
0.271782, 0.277561, 0.271433, 0.277126, 0.271090, 0.276699, 0.270755, 0.276279, 0.270426, 0.275867, /* 12000 */
0.270103, 0.275462, 0.269787, 0.275065, 0.269476, 0.274674, 0.269172, 0.274290, 0.268874, 0.273913,
0.268581, 0.273542, 0.268293, 0.273178, 0.268011, 0.272820, 0.267734, 0.272467, 0.267462, 0.272121, /* 13000 */
0.267195, 0.271780, 0.266933, 0.271445, 0.266676, 0.271116, 0.266423, 0.270791, 0.266174, 0.270472,
0.265930, 0.270158, 0.265690, 0.269849, 0.265454, 0.269545, 0.265223, 0.269246, 0.264995, 0.268952, /* 14000 */
0.264771, 0.268662, 0.264550, 0.268376, 0.264334, 0.268095, 0.264121, 0.267818, 0.263911, 0.267545,
0.263705, 0.267277, 0.263502, 0.267012, 0.263302, 0.266751, 0.263106, 0.266495, 0.262912, 0.266241, /* 15000 */
0.262722, 0.265992, 0.262534, 0.265746, 0.262350, 0.265504, 0.262168, 0.265265, 0.261989, 0.265030,
0.261813, 0.264798, 0.261640, 0.264569, 0.261469, 0.264343, 0.261300, 0.264121, 0.261134, 0.263901, /* 16000 */
0.260971, 0.263685, 0.260809, 0.263471, 0.260651, 0.263261, 0.260494, 0.263053, 0.260340, 0.262848,
0.260188, 0.262646, 0.260038, 0.262446, 0.259890, 0.262249, 0.259744, 0.262055, 0.259600, 0.261863, /* 17000 */
0.259458, 0.261674, 0.259318, 0.261487, 0.259180, 0.261302, 0.259044, 0.261120, 0.258910, 0.260940,
0.258778, 0.260762, 0.258647, 0.260587, 0.258518, 0.260414, 0.258390, 0.260243, 0.258265, 0.260074, /* 18000 */
0.258141, 0.259907, 0.258018, 0.259742, 0.257897, 0.259579, 0.257778, 0.259418, 0.257660, 0.259259,
0.257544, 0.259102, 0.257429, 0.258947, 0.257315, 0.258793, 0.257203, 0.258642, 0.257093, 0.258492, /* 19000 */
0.256983, 0.258344, 0.256875, 0.258197, 0.256768, 0.258052, 0.256663, 0.257909, 0.256559, 0.257768,
0.256456, 0.257628 /* 20000 */
};
const MATFLOAT *cs_get_gamma(enum cs_gamma_type gamma_type)
{
return cs_vec_gamma[(gamma_type < EGT_CUSTOM) ? gamma_type : EGT_LINEAR];
}
const MATFLOAT *cs_get_color_space(enum cs_color_space_type color_space_type)
{
return cs_vec_color_space[(color_space_type < ECST_CUSTOM) ? color_space_type : ECST_709];
}
const MATFLOAT *cs_get_white_point(enum cs_white_point_type white_point_type)
{
return cs_vec_white_point[(white_point_type < EWPT_NUM) ? white_point_type : EWPT_NONE];
}
void cs_set_opts_def(struct s_cs_opts *ptr_cs_opts)
{
int ni;
ptr_cs_opts->color_space_type = ECST_709;
ptr_cs_opts->gamma_type = EGT_709;
ptr_cs_opts->mode = 0;
ptr_cs_opts->pq_norm = 0.0;
ptr_cs_opts->luminance_limits[0] = 0.0;
ptr_cs_opts->luminance_limits[1] = 400.0;
for (ni = 0; ni < 8; ni++)
ptr_cs_opts->rgbw_xy[ni] = cs_get_color_space(ECST_709)[ni];
for (ni = 0; ni < 4; ni++)
ptr_cs_opts->gamma_parm[ni] = cs_get_gamma(EGT_LINEAR)[ni];
}
void cs_init(struct s_cs_opts *ptr_cs_opts, struct s_color_space *ptr_color_space)
{
int ni;
ptr_color_space->color_space_type = ptr_cs_opts->color_space_type;
ptr_color_space->gamma_type = ptr_cs_opts->gamma_type;
ptr_color_space->mode = ptr_cs_opts->mode;
ptr_color_space->pq_norm = (ptr_cs_opts->pq_norm > 0.0) ?
cs_gamma_pq(ptr_cs_opts->pq_norm / CS_MAX_LUMINANCE, EGD_LIN_2_NONLIN) : 0.0;
ptr_color_space->luminance_limits[0] = (MATFLOAT)ptr_cs_opts->luminance_limits[0] / CS_MAX_LUMINANCE;
ptr_color_space->luminance_limits[1] = (MATFLOAT)ptr_cs_opts->luminance_limits[1] / CS_MAX_LUMINANCE;
ptr_color_space->luminance_limits[2] = ptr_color_space->luminance_limits[1] -
ptr_color_space->luminance_limits[0];
for (int ni = 0; ni < 8; ni++)
ptr_color_space->rgbw_xy[ni] = (ptr_cs_opts->color_space_type < ECST_CUSTOM) ?
cs_get_color_space(ptr_cs_opts->color_space_type)[ni] : ptr_cs_opts->rgbw_xy[ni];
for (ni = 0; ni < 4; ni++)
ptr_color_space->gamma_parm[ni] = (ptr_cs_opts->gamma_type < EGT_CUSTOM) ?
cs_get_gamma(ptr_cs_opts->gamma_type)[ni] : (MATFLOAT)ptr_cs_opts->gamma_parm[ni];
cs_init_private(ptr_color_space);
}
void cs_init_private(struct s_color_space *ptr_color_space)
{
static MATFLOAT mat_xyz2lms[3][3] = {
/* ITU-R BT.2390-4, p36. */
{ 0.3592, 0.6976, -0.0358},
{-0.1922, 1.1004, 0.0755},
{ 0.0070, 0.0749, 0.8434}
};
static MATFLOAT mat_lms2xyz[3][3] = {
/* ITU-R BT.2390-4, p36. */
{ 2.0701800566956132, -1.3264568761030211, 0.2066160068478551},
{ 0.3649882500326574, 0.6804673628522352, -0.0454217530758532},
{-0.0495955422389321, -0.0494211611867575, 1.1879959417328037}
};
static MATFLOAT mat_lms2itp[3][3] = {
/* ITU-R BT.2020, BT.2390-4, p.36 */
{ 0.5, 0.5, 0.0},
{ 6610.0 / 4096.0, -13613.0 / 4096.0, 7003.0 / 4096.0},
{17933.0 / 4096.0, -17390.0 / 4096.0, -543.0 / 4096.0}
};
static MATFLOAT mat_itp2lms[3][3] = {
/* ITU-R BT.2020, BT.2390-4, p.36 */
{1.0, 0.00860903703793276, 0.11102962500302596},
{1.0, -0.00860903703793276, -0.11102962500302596},
{1.0, 0.56003133571067909, -0.32062717498731885}
};
int ni, nj;
cs_luminance_to_luma_limits(ptr_color_space->luminance_limits, ptr_color_space->luma_limits);
mat_3x3_unity(ptr_color_space->mat_chad);
/* set white point */
ptr_color_space->white_xyz[0] = ptr_color_space->rgbw_xy[6];
ptr_color_space->white_xyz[1] = ptr_color_space->rgbw_xy[7];
ptr_color_space->white_xyz[2] = 1.0;
cs_xyy_to_xyz(ptr_color_space->white_xyz, ptr_color_space->white_xyz);
/* generate RGB to XYZ and back matrixes */
cs_genmat_rgb_to_xyz(ptr_color_space->rgbw_xy, ptr_color_space->mat_rgb2xyz);
if (ptr_color_space->mode & CS_CHAD_D65) {
/* Chromatic Adaptation from Color Space to D65 (BT.2020) */
MATFLOAT mat_tmp[3][3];
cs_genmat_chad(&ptr_color_space->rgbw_xy[6], (MATFLOAT *)cs_get_white_point(EWPT_D65),
ptr_color_space->mat_chad);
mat_copy3x3(ptr_color_space->mat_rgb2xyz, mat_tmp);
mat_mul3x3(ptr_color_space->mat_chad, mat_tmp, ptr_color_space->mat_rgb2xyz);
}
mat_inv3x3(ptr_color_space->mat_rgb2xyz, ptr_color_space->mat_xyz2rgb);
for (ni = 0; ni < 3; ni++)
for (nj = 0; nj < 3; nj++) {
ptr_color_space->mat_lms2itp[ni][nj] = mat_lms2itp[ni][nj];
ptr_color_space->mat_itp2lms[ni][nj] = mat_itp2lms[ni][nj];
}
mat_mul3x3(mat_xyz2lms, ptr_color_space->mat_rgb2xyz, ptr_color_space->mat_rgb2lms);
mat_mul3x3(ptr_color_space->mat_xyz2rgb, mat_lms2xyz, ptr_color_space->mat_lms2rgb);
ptr_color_space->cct = cs_xy_to_cct(&ptr_color_space->rgbw_xy[6]);
ptr_color_space->hlg_system_gamma = cs_hlg_system_gamma(ptr_color_space->luminance_limits[1]);
ptr_color_space->hlg_beta = mat_sqrt(3.0 * mat_pow(ptr_color_space->luminance_limits[0] /
ptr_color_space->luminance_limits[1], 1.0 / ptr_color_space->hlg_system_gamma));
}
void cs_copy(struct s_color_space *ptr_color_space_src, struct s_color_space *ptr_color_space_dst)
{
ptr_color_space_dst->color_space_type = ptr_color_space_src->color_space_type;
ptr_color_space_dst->gamma_type = ptr_color_space_src->gamma_type;
ptr_color_space_dst->mode = ptr_color_space_src->mode;
ptr_color_space_dst->pq_norm = ptr_color_space_src->pq_norm;
int ni, nj;
for (ni = 0; ni < 3; ni++)
ptr_color_space_dst->luminance_limits[ni] = ptr_color_space_src->luminance_limits[ni];
for (ni = 0; ni < 8; ni++)
ptr_color_space_dst->rgbw_xy[ni] = ptr_color_space_src->rgbw_xy[ni];
for (ni = 0; ni < 4; ni++)
ptr_color_space_dst->gamma_parm[ni] = ptr_color_space_src->gamma_parm[ni];
for (ni = 0; ni < 3; ni++)
for (nj = 0; nj < 3; nj++) {
ptr_color_space_dst->mat_rgb2xyz[ni][nj] = ptr_color_space_src->mat_rgb2xyz[ni][nj];
ptr_color_space_dst->mat_xyz2rgb[ni][nj] = ptr_color_space_src->mat_xyz2rgb[ni][nj];
ptr_color_space_dst->mat_chad[ni][nj] = ptr_color_space_src->mat_chad[ni][nj];
ptr_color_space_dst->mat_rgb2lms[ni][nj] = ptr_color_space_src->mat_rgb2lms[ni][nj];
ptr_color_space_dst->mat_lms2rgb[ni][nj] = ptr_color_space_src->mat_lms2rgb[ni][nj];
ptr_color_space_dst->mat_lms2itp[ni][nj] = ptr_color_space_src->mat_lms2itp[ni][nj];
ptr_color_space_dst->mat_itp2lms[ni][nj] = ptr_color_space_src->mat_itp2lms[ni][nj];
}
for (ni = 0; ni < 3; ni++)
ptr_color_space_dst->white_xyz[ni] = ptr_color_space_src->white_xyz[ni];
ptr_color_space_dst->cct = ptr_color_space_src->cct;
}
void cs_luminance_to_luma_limits(MATFLOAT luminance_limits[2], MATFLOAT luma_limits[3])
{
luma_limits[0] = cs_gamma_pq(luminance_limits[0], EGD_LIN_2_NONLIN);
luma_limits[1] = cs_gamma_pq(luminance_limits[1], EGD_LIN_2_NONLIN);
luma_limits[2] = luma_limits[1] - luma_limits[0];
}
void cs_xyy_to_xyz(MATFLOAT xyy_inp[3], MATFLOAT xyz_out[3])
{ /* output may be the same as input */
MATFLOAT xyy_tmp[3];
mat_copy(xyy_inp, xyy_tmp, 3);
xyz_out[0] = (xyy_tmp[1] > 0.0) ? xyy_tmp[2] * xyy_tmp[0] / xyy_tmp[1] : 0.0;
xyz_out[1] = xyy_tmp[2];
xyz_out[2] = (xyy_tmp[1] > 0.0) ? xyy_tmp[2] * (1.0 - xyy_tmp[0] - xyy_tmp[1]) / xyy_tmp[1] : 0.0;
}
void cs_xyz_to_xyy(MATFLOAT xyz_inp[3], MATFLOAT xyy_out[3])
{ /* output may be the same as input */
MATFLOAT sum = xyz_inp[0] + xyz_inp[1] + xyz_inp[2];
xyy_out[2] = xyz_inp[1];
xyy_out[1] = (sum > 0.0) ? xyz_inp[1] / sum : 0.0;
xyy_out[0] = (sum > 0.0) ? xyz_inp[0] / sum : 0.0;
}
void cs_xyzc_to_xyz(MATFLOAT xyz_inp[3], MATFLOAT xyz_out[3])
{ /* output may be the same as input */
MATFLOAT sum = xyz_inp[0] + xyz_inp[1] + xyz_inp[2];
xyz_out[0] = (sum > 0.0) ? xyz_inp[0] / sum : 0.0;
xyz_out[1] = (sum > 0.0) ? xyz_inp[1] / sum : 0.0;
xyz_out[2] = 1.0 - xyz_out[0] - xyz_out[1];
}
void cs_xyz_to_xyzc(MATFLOAT xyz_inp[3], MATFLOAT xyz_out[3])
{ /* output may be the same as input */
MATFLOAT xyz_tmp[3];
mat_copy(xyz_inp, xyz_tmp, 3);
xyz_out[0] = (xyz_tmp[1] > 0.0) ? xyz_tmp[0] / xyz_tmp[1] : 0.0;
xyz_out[1] = 1.0;
xyz_out[2] = (xyz_tmp[1] > 0.0) ? xyz_tmp[2] / xyz_tmp[1] : 0.0;
}
void cs_rgb_to_itp(struct s_color_space *ptr_color_space, MATFLOAT rgb_inp[3], MATFLOAT itp_out[3])
{ /* output may be the same as input */
MATFLOAT lms[3];
int nc;
mat_eval_3x3(ptr_color_space->mat_rgb2lms, rgb_inp, lms);
for (nc = 0; nc < 3; nc++)
lms[nc] = cs_gamma_pq(lms[nc], EGD_LIN_2_NONLIN);
mat_eval_3x3(ptr_color_space->mat_lms2itp, lms, itp_out);
}
void cs_itp_to_rgb(struct s_color_space *ptr_color_space, MATFLOAT itp_inp[3], MATFLOAT rgb_out[3])
{ /* output may be the same as input */
MATFLOAT lms[3];
int nc;
mat_eval_3x3(ptr_color_space->mat_itp2lms, itp_inp, lms);
for (nc = 0; nc < 3; nc++)
lms[nc] = cs_gamma_pq(lms[nc], EGD_NONLIN_2_LIN);
mat_eval_3x3(ptr_color_space->mat_lms2rgb, lms, rgb_out);
}
void cs_ich_to_itp(MATFLOAT ich_inp[3], MATFLOAT itp_out[3])
{ /* output must not be the same as input */
itp_out[0] = ich_inp[0];
itp_out[1] = ich_inp[1] * mat_cos(ich_inp[2]);
itp_out[2] = ich_inp[1] * mat_sin(ich_inp[2]);
}
void cs_itp_to_ich(MATFLOAT itp_inp[3], MATFLOAT ich_out[3])
{ /* output must not be the same as input */
ich_out[0] = itp_inp[0];
ich_out[1] = mat_radius(itp_inp[2], itp_inp[1]);
ich_out[2] = mat_angle(itp_inp[2], itp_inp[1]);
}
void cs_rgb_to_yuv(MATFLOAT rgb_inp[3], MATFLOAT yuv_out[3])
{ /* RGB to YCbCr709 from Charles Poynton "Digital Video and HD: Algorithms and Interfaces", p.371 */
static MATFLOAT vec_off_inp[3] = { 0.0, 0.0, 0.0 };
static MATFLOAT vec_off_out[3] = { 0.0, 0.5, 0.5 };
static MATFLOAT mat_rgb_to_yuv[3][3] = {
/* R G B */
{ 0.2126, 0.7152, 0.0722 },
{ -0.11457211, -0.38542789, 0.5 },
{ 0.5, -0.45415291, -0.04584709}
};
mat_eval_off_3x3_off(vec_off_inp, mat_rgb_to_yuv, vec_off_out, rgb_inp, yuv_out);
cs_clamp_rgb(yuv_out, 0.0, 1.0);
}
void cs_yuv_to_rgb(MATFLOAT yuv_inp[3], MATFLOAT rgb_out[3])
{ /* YCbCr709 to RGB from Charles Poynton "Digital Video and HD: Algorithms and Interfaces", p.371 */
static MATFLOAT vec_off_inp[3] = { 0.0, -0.5, -0.5 };
static MATFLOAT vec_off_out[3] = { 0.0, 0.0, 0.0 };
static MATFLOAT mat_yuv_to_rgb[3][3] = {
/* Y Cb Cr */
{ 1.0, 0.0, 1.5748 },
{ 1.0, -0.187324273, -0.468124273 },
{ 1.0, 1.8556, 0.0 }
};
mat_eval_off_3x3_off(vec_off_inp, mat_yuv_to_rgb, vec_off_out, yuv_inp, rgb_out);
cs_clamp_rgb(rgb_out, 0.0, 1.0);
}
void cs_nlin_to_lin_rgb(struct s_color_space *ptr_color_space, MATFLOAT rgb_inp[3], MATFLOAT rgb_out[3])
{
if (ptr_color_space->gamma_type == EGT_HLG)
cs_hlg_eotf(rgb_inp, rgb_out, ptr_color_space->luminance_limits,
ptr_color_space->hlg_system_gamma, ptr_color_space->hlg_beta);
else
for (int nc = 0; nc < 3; nc++)
rgb_out[nc] = cs_nlin_to_lin(ptr_color_space, rgb_inp[nc]);
}
MATFLOAT cs_nlin_to_lin(struct s_color_space *ptr_color_space, MATFLOAT val_inp)
{
MATFLOAT val_out;
if (ptr_color_space->gamma_type == EGT_PQ) {
/* HDR PQ encoded signal is normilized to a range [0.0,1.0],
where 0.0 mapped to 0.0 and 1.0 mapped to PQ-1(pq_norm) */
if (ptr_color_space->pq_norm > 0.0)
val_out = mat_denorm(val_inp, 0.0, ptr_color_space->pq_norm);
else
val_out = val_inp;
val_out = mat_clamp(val_out, 0.0, 1.0);
val_out = cs_gamma(val_out, ptr_color_space->gamma_parm, EGD_NONLIN_2_LIN);
}
else {
/* SDR encoded signal is normilized to a range [0.0,1.0],
where 0.0 mapped to Black (0,0,0) and 1.0 mapped to White (1,1,1) */
val_out = cs_gamma(val_inp, ptr_color_space->gamma_parm, EGD_NONLIN_2_LIN);
val_out = mat_denorm(val_out, ptr_color_space->luminance_limits[0], ptr_color_space->luminance_limits[2]);
val_out = mat_clamp(val_out, 0.0, 1.0);
}
return val_out;
}
void cs_lin_to_nlin_rgb(struct s_color_space *ptr_color_space, MATFLOAT rgb_inp[3], MATFLOAT rgb_out[3])
{
if (ptr_color_space->gamma_type == EGT_HLG)
cs_hlg_oetf(rgb_inp, rgb_out, ptr_color_space->luminance_limits[1], ptr_color_space->hlg_system_gamma);
else
for (int nc = 0; nc < 3; nc++)
rgb_out[nc] = cs_lin_to_nlin(ptr_color_space, rgb_inp[nc]);
}
MATFLOAT cs_lin_to_nlin(struct s_color_space *ptr_color_space, MATFLOAT val_inp)
{
MATFLOAT val_out;
if (ptr_color_space->gamma_type == EGT_PQ) {
/* HDR PQ encoded signal is normilized to a range [0.0,1.0],
where 0.0 mapped to 0.0 and 1.0 mapped to PQ-1(pq_norm) */
val_out = cs_gamma(val_inp, ptr_color_space->gamma_parm, EGD_LIN_2_NONLIN);
if (ptr_color_space->pq_norm > 0.0)
val_out = mat_norm(val_out, 0.0, ptr_color_space->pq_norm);
val_out = mat_clamp(val_out, 0.0, 1.0);
}
else {
/* SDR encoded signal is normilized to a range [0.0,1.0],
where 0.0 mapped to Black (0,0,0) and 1.0 mapped to White (1,1,1) */
val_out = mat_norm(val_inp, ptr_color_space->luminance_limits[0], ptr_color_space->luminance_limits[2]);
val_out = mat_clamp(val_out, 0.0, 1.0);
val_out = cs_gamma(val_out, ptr_color_space->gamma_parm, EGD_LIN_2_NONLIN);
}
return val_out;
}
int cs_genmat_rgb_to_xyz(MATFLOAT rgbw[8], MATFLOAT mat_rgb2xyz[3][3])
{
MATFLOAT white_xyz[3] = { rgbw[6], rgbw[7], 1.0 };
MATFLOAT mat[3][3], mat_inv[3][3], white_k[3];
int ni, nc;
int rc;
for (ni = 0; ni < 3; ni++) { /* X, Y, Z */
mat[0][ni] = rgbw[2 * ni + 0] / rgbw[2 * ni + 1];
mat[1][ni] = 1.0;
mat[2][ni] = (1.0 - rgbw[2 * ni + 0] - rgbw[2 * ni + 1]) / rgbw[2 * ni + 1];
}
rc = mat_inv3x3(mat, mat_inv);
cs_xyy_to_xyz(white_xyz, white_xyz);
mat_eval_3x3(mat_inv, white_xyz, white_k);
for (ni = 0; ni < 3; ni++)
for (nc = 0; nc < 3; nc++)
mat_rgb2xyz[nc][ni] = white_k[ni] * mat[nc][ni];
return rc;
}
int cs_genmat_xyz_to_rgb(MATFLOAT rgbw_xy[8], MATFLOAT mat_xyz2rgb[3][3])
{
MATFLOAT mat_rgb2xyz[3][3];
cs_genmat_rgb_to_xyz(rgbw_xy, mat_rgb2xyz);
return mat_inv3x3(mat_rgb2xyz, mat_xyz2rgb);
}
int cs_genmat_rgb_to_rgb(MATFLOAT rgbw_xy_src[8], MATFLOAT rgbw_xy_dst[8], MATFLOAT mat_rgb2rgb[3][3], int en_chad)
{
MATFLOAT mat_rgb2xyz[3][3], mat_xyz2rgb[3][3], mat_chad[3][3];
int rc;
cs_genmat_rgb_to_xyz(rgbw_xy_src, mat_rgb2xyz);
rc = cs_genmat_xyz_to_rgb(rgbw_xy_dst, mat_xyz2rgb);
if (en_chad) { /* Chromatic Adaptation */
MATFLOAT mat_tmp[3][3];
cs_genmat_chad(&rgbw_xy_src[6], &rgbw_xy_dst[6], mat_chad);
mat_copy3x3(mat_rgb2xyz, mat_tmp);
mat_mul3x3(mat_chad, mat_tmp, mat_rgb2xyz);
}
mat_mul3x3(mat_xyz2rgb, mat_rgb2xyz, mat_rgb2rgb);
return rc;
}
int cs_genmat_chad(MATFLOAT white_xy_src[2], MATFLOAT white_xy_dst[2], MATFLOAT mat_chad[3][3])
{
static MATFLOAT mat_bradford[3][3] = {
/* Bradford matrix */
{ 0.8951000, 0.2664000, -0.1614000},
{-0.7502000, 1.7135000, 0.0367000},
{ 0.0389000, -0.0685000, 1.0296000}
};
static MATFLOAT mat_bradford_inv[3][3] = {
/* Bradford inverse matrix */
{ 0.9869929, -0.1470543, 0.1599627},
{ 0.4323053, 0.5183603, 0.0492912},
{-0.0085287, 0.0400428, 0.9684867}
};
#if 0 /* Not in used */
static MATFLOAT mat_von_kries[3][3] = {
/* Von Kries matrix */
{ 0.4002400, 0.7076000, -0.0808100},
{-0.2263000, 1.1653200, 0.0457000},
{ 0.0000000, 0.0000000, 0.9182200}
};
static MATFLOAT mat_von_kries_inv[3][3] = {
/* Von Kries inverse matrix */
{1.8599364, -1.1293816, 0.2198974},
{0.3611914, 0.6388125, -0.0000064},
{0.0000000, 0.0000000, 1.0890636}
};
#endif
MATFLOAT vec_white_xyz_src[3] = { white_xy_src[0], white_xy_src[1], 1.0 };
MATFLOAT vec_white_xyz_dst[3] = { white_xy_dst[0], white_xy_dst[1], 1.0 };
MATFLOAT vec_lms[3][3];
MATFLOAT rgb_src[3], rgb_dst[3];
MATFLOAT mat_tmp[3][3];
int nc;
/* convert to XYZ */
cs_xyy_to_xyz(vec_white_xyz_src, vec_white_xyz_src);
cs_xyy_to_xyz(vec_white_xyz_dst, vec_white_xyz_dst);
/* generate scales */
mat_3x3_unity(vec_lms);
mat_eval_3x3(mat_bradford, vec_white_xyz_src, rgb_src);
mat_eval_3x3(mat_bradford, vec_white_xyz_dst, rgb_dst);
for (nc = 0; nc < 3; nc++)
vec_lms[nc][nc] = rgb_dst[nc] / rgb_src[nc];
/* normalize */
mat_mul3x3(vec_lms, mat_bradford, mat_tmp);
mat_mul3x3(mat_bradford_inv, mat_tmp, mat_chad);
return 0;
}
MATFLOAT cs_gamma(MATFLOAT val, MATFLOAT gamma_parm[4], enum cs_gamma_dir gamma_dir)
{
MATFLOAT val_out;
if (gamma_parm[0] == 0.0)
val_out = cs_gamma_pq(val, gamma_dir);
else if (gamma_parm[0] == 0.5)
val_out = cs_gamma_hlg(val, gamma_dir);
else {
MATFLOAT c1 = gamma_parm[0];
MATFLOAT c2 = gamma_parm[1];
MATFLOAT c3 = gamma_parm[2];
MATFLOAT c4 = gamma_parm[3];
if (gamma_dir == EGD_LIN_2_NONLIN)
val_out = ((val < c4) ? val * c3 : c1 * mat_pow(val, c2) + 1.0 - c1);
else
val_out = (val < c4 * c3) ? val / c3 : mat_pow((val + c1 - 1.0) / c1, 1.0 / c2);
}
return val_out;
}
/* R_REC-BT.2100-2-2 Table 4 */
/* input must be in arange [0,1] normilized to [0,10000]cd/m^2 in linear or non-linear space */
/* output must be in a range [0,1] normilized to [0,10000]cd/m^2 in linear or non-linear space */
MATFLOAT cs_gamma_pq(MATFLOAT val, enum cs_gamma_dir gamma_dir)
{
static const MATFLOAT s_m1 = 0.1593017578125;
static const MATFLOAT s_m2 = 78.84375;
static const MATFLOAT s_c1 = 0.8359375;
static const MATFLOAT s_c2 = 18.8515625;
static const MATFLOAT s_c3 = 18.6875;
MATFLOAT sign = (val < 0.0) ? -1.0 : 1.0;
MATFLOAT val_out = MAT_ABS(val);
MATFLOAT t1, t2, t;
if (gamma_dir == EGD_LIN_2_NONLIN) { /* linear to PQ */
MATFLOAT x = mat_pow(val_out, s_m1);
t1 = (s_c2 * x) + s_c1;
t2 = 1.0 + (s_c3 * x);
t = t1 / t2;
val_out = mat_pow(t, s_m2);
} else { /* PQ to linear */
MATFLOAT np = mat_pow(val_out, 1.0 / s_m2);
t1 = np - s_c1;
t1 = MAT_MAX(t1, 0.0);
t2 = s_c2 - (s_c3 * np);
t = t1 / t2;
val_out = mat_pow(t, 1.0 / s_m1);
}
val_out *= sign;
return val_out;
}
/* EOTF 1886 */
/* input must be in arange [0,1] normilized to [Lb,Lw]cd/m^2 in non-linear space */
/* output must be in arange [0,1] normilized to [0,10000]cd/m^2 in linear space */
/* lb in a range [0,1] normalized to [0,10000]cd/m^2 in linear space */
/* lw in a range [0,1] normalized to [0,10000]cd/m^2 in linear space */
MATFLOAT cs_gamma_1886(MATFLOAT val, MATFLOAT lb, MATFLOAT lw, MATFLOAT gamma)
{
MATFLOAT lb_nl = mat_pow(lb, 1.0 / gamma);
MATFLOAT lw_nl = mat_pow(lw, 1.0 / gamma);
MATFLOAT a = mat_pow(lw_nl - lb_nl, gamma);
MATFLOAT b = lb_nl / (lw_nl - lb_nl);
return a * mat_pow(MAT_MAX(val + b, 0.0), gamma);
}
/* rgb_inp[] in a range [0,1] normalized to [0,10000]cd/m^2 in linear space */
/* rgb_out[] in a range [0,1] normalized to [0,10000]cd/m^2 in linear space */
void cs_pq_ootf(MATFLOAT rgb_inp[3], MATFLOAT rgb_out[3])
{
int nc;
for (nc = 0; nc < 3; nc++) {
MATFLOAT e = rgb_inp[nc] * 59.5208;
MATFLOAT e709 = (e <= 0.018) ? 4.5 * e : 1.099 * mat_pow(e, 0.45) - 0.099; /* OETF 709 */
MATFLOAT e1886 = mat_pow(e709, 2.4) / 100.0; /* EOTF 1886 */
rgb_out[nc] = MAT_CLAMP(e1886, 0.0, 1.0);
}
}
/* BT.2390 display referred */
/* rgb_inp[] in a range [0,1] normalized to [0,100]cd/m^2 in non-linear space */
/* rgb_out[] in a range [0,1] normalized to [0,10000]cd/m^2 in non-linear space */
void cs_sdr_to_pq(MATFLOAT rgb_inp[3], MATFLOAT rgb_out[3], MATFLOAT en_709_2020)
{
MATFLOAT sdr_lb = 0.0;
MATFLOAT sdr_lw = 100.0 / CS_MAX_LUMINANCE;
MATFLOAT sdr_gamma = 2.4;
MATFLOAT scale = 2.0;
MATFLOAT rgb_lin[3];
int nc;
for (nc = 0; nc < 3; nc++)
rgb_lin[nc] = cs_gamma_1886(rgb_inp[nc], sdr_lb, sdr_lw, sdr_gamma); /* [0,10000]cd/m^2 */
if (en_709_2020) {
MATFLOAT rgb_tmp[3];
mat_copy(rgb_lin, rgb_tmp, 3);
mat_eval_3x3(cs_mat_709_2020, rgb_tmp, rgb_lin); /* [0,10000]cd/m^2 */
}
for (nc = 0; nc < 3; nc++)
rgb_lin[nc] = rgb_lin[nc] * scale; /* scale to 200cd/m^2 */
cs_gamma_rgb(rgb_lin, rgb_out, (MATFLOAT *)cs_get_gamma(EGT_PQ), EGD_LIN_2_NONLIN); /* [0,10000]cd/m^2 */
}
void cs_gamma_rgb(MATFLOAT rgb_inp[3], MATFLOAT rgb_out[3], MATFLOAT gamma_parm[4], enum cs_gamma_dir gamma_dir)
{ /* output may be the same as input */
int nc;
for (nc = 0; nc < 3; nc++)
rgb_out[nc] = cs_gamma(rgb_inp[nc], gamma_parm, gamma_dir);
}
int cs_min_rgb(MATFLOAT rgb[3], MATFLOAT val_min)
{
int is_clip = 0;
int nc;
for (nc = 0; nc < 3; nc++) {
MATFLOAT value = rgb[nc];
rgb[nc] = MAT_MAX(value, val_min);
is_clip |= (rgb[nc] == value) ? 0 : 1;
}
return is_clip;
}
int cs_max_rgb(MATFLOAT rgb[3], MATFLOAT val_max)
{
int is_clip = 0;
int nc;
for (nc = 0; nc < 3; nc++) {
MATFLOAT value = rgb[nc];
rgb[nc] = MAT_MIN(value, val_max);
is_clip |= (rgb[nc] == value) ? 0 : 1;
}
return is_clip;
}
int cs_is_valid_ic(struct s_color_space *ptr_color_space, MATFLOAT pnt_ic[2], MATFLOAT hue_sin_cos[2])
{
MATFLOAT pnt_itp[3];
pnt_itp[0] = pnt_ic[0];
pnt_itp[1] = pnt_ic[1] * hue_sin_cos[1];
pnt_itp[2] = pnt_ic[1] * hue_sin_cos[0];
return cs_is_valid_itp(ptr_color_space, pnt_itp);
}
int cs_is_valid_itp(struct s_color_space *ptr_color_space, MATFLOAT itp[3])
{
MATFLOAT rgb[3];
cs_itp_to_rgb(ptr_color_space, itp, rgb);
return cs_is_valid_rgb(rgb, ptr_color_space->luminance_limits[0], ptr_color_space->luminance_limits[1]);
}
int cs_is_valid_rgb(MATFLOAT rgb[3], MATFLOAT val_min, MATFLOAT val_max)
{
return mat_is_valid_vec(rgb, 3, val_min, val_max);
}
int cs_clip_rgb(MATFLOAT rgb[3], MATFLOAT val_min, MATFLOAT val_max)
{
int is_clip = cs_is_valid_rgb(rgb, val_min, val_max);
if (is_clip == 0)
cs_clamp_rgb(rgb, val_min, val_max);
return is_clip ? 0 : 1;
}
void cs_clamp_rgb(MATFLOAT rgb[3], MATFLOAT val_min, MATFLOAT val_max)
{
int nc;
for (nc = 0; nc < 3; nc++)
rgb[nc] = mat_clamp(rgb[nc], val_min, val_max);
}
void cs_norm_rgb(MATFLOAT rgb[3], MATFLOAT val_min, MATFLOAT val_rng)
{
int nc;
for (nc = 0; nc < 3; nc++)
rgb[nc] = mat_norm(rgb[nc], val_min, val_rng);
}
void cs_denorm_rgb(MATFLOAT rgb[3], MATFLOAT val_min, MATFLOAT val_rng)
{
int nc;
for (nc = 0; nc < 3; nc++)
rgb[nc] = mat_denorm(rgb[nc], val_min, val_rng);
}
void cs_int2flt_rgb(int rgb_inp[3], MATFLOAT rgb_out[3], int val_max)
{
int nc;
for (nc = 0; nc < 3; nc++)
rgb_out[nc] = mat_int2flt(rgb_inp[nc], val_max);
}
void cs_flt2int_rgb(MATFLOAT rgb_inp[3], int rgb_out[3], int val_max)
{
int nc;
for (nc = 0; nc < 3; nc++)
rgb_out[nc] = mat_flt2int(rgb_inp[nc], val_max);
}
void cs_short2flt_rgb(unsigned short rgb_inp[3], MATFLOAT rgb_out[3], int val_max)
{
int nc;
for (nc = 0; nc < 3; nc++)
rgb_out[nc] = mat_int2flt(rgb_inp[nc], val_max);
}
void cs_flt2short_rgb(MATFLOAT rgb_inp[3], unsigned short rgb_out[3], int val_max)
{
int nc;
for (nc = 0; nc < 3; nc++)
rgb_out[nc] = mat_flt2int(rgb_inp[nc], val_max);
}
void cs_genprim_itp(struct s_color_space *ptr_color_space, int num_prim,
MATFLOAT *ptr_prim_rgb, MATFLOAT *ptr_prim_ich)
{
int nk, nc;
for (nk = 0; nk < num_prim; nk++) {
MATFLOAT rgb[3], vec_itp[3], vec_ich[3];
mat_copy(&ptr_prim_rgb[3 * nk], rgb, 3);
cs_denorm_rgb(rgb, ptr_color_space->luminance_limits[0], ptr_color_space->luminance_limits[2]);
cs_rgb_to_itp(ptr_color_space, rgb, vec_itp);
cs_itp_to_ich(vec_itp, vec_ich);
for (nc = 0; nc < 3; nc++)
ptr_prim_ich[num_prim * nc + nk] = vec_ich[nc];
}
}
MATFLOAT cs_soft_clip(MATFLOAT val, MATFLOAT limits_src[3], MATFLOAT limits_dst[3])
{ /* Based on BT.2390 - Src must be wider then Dst */
const MATFLOAT epsilon = 0.000001;
MATFLOAT val_min = (limits_dst[0] - limits_src[0]) / (limits_src[1] - limits_src[0]);
MATFLOAT val_max = (limits_dst[1] - limits_src[0]) / (limits_src[1] - limits_src[0]);
MATFLOAT ks = (1.5 * val_max) - 0.5;
MATFLOAT e0, e1, e2, e3, e4;
/* Input value must be normilized to [0.0,1.0] */
e0 = val;
e1 = mat_norm(e0, limits_src[0], limits_src[2]);
e1 = mat_clamp(e1, 0.0, 1.0);
if (e1 < ks)
e2 = e1;
else {
MATFLOAT t = ((1.0 - ks) <= epsilon) ? (e1 - ks) : ((e1 - ks) / (1.0 - ks));
MATFLOAT t2 = t * t;
MATFLOAT t3 = t2 * t;
e2 = (((2.0 * t3) - (3.0 * t2) + 1.0) * ks) + ((t3 - (2.0 * t2) + t) * (1.0 - ks)) + (((-2.0 * t3) +
(3.0 * t2)) * val_max);
}
e3 = e2 + val_min * mat_pow((1.0 - e2), 4.0);
/* Output value must be denormilized back to [limits_src[0], limits_src[1]] */
e4 = mat_denorm(e3, limits_src[0], limits_src[2]);
e4 = mat_clamp(e4, limits_src[0], limits_src[1]);
return e4;
}
MATFLOAT cs_gamma_to_gamma(MATFLOAT val, enum cs_gamma_type gamma_type_src, enum cs_gamma_type gamma_type_dst,
MATFLOAT luminance_limits_dst[3], MATFLOAT luma_limits_src[3], MATFLOAT luma_limits_dst[3],
MATFLOAT(*func_pq_to_pq)(MATFLOAT), int en_norm, int en_soft_clip)
{
MATFLOAT val_out = cs_gamma(val, (MATFLOAT *)cs_get_gamma(gamma_type_src), EGD_NONLIN_2_LIN); /* degamma */
if (en_norm)
val_out = mat_denorm(val_out, luminance_limits_dst[0], luminance_limits_dst[2]);/* denorm */
val_out = mat_clamp(val_out, luminance_limits_dst[0], luminance_limits_dst[1]); /* clamp */
val_out = cs_gamma_pq(val_out, EGD_LIN_2_NONLIN); /* LIN2PQ */
val_out = func_pq_to_pq(val_out); /* PQ2PQ transform */
if (en_soft_clip)
val_out = cs_soft_clip(val_out, luma_limits_src, luma_limits_dst); /* SoftClip */
val_out = cs_gamma_pq(val_out, EGD_NONLIN_2_LIN); /* PQ2LIN */
if (en_norm)
val_out = mat_norm(val_out, luminance_limits_dst[0], luminance_limits_dst[2]); /* norm */
val_out = mat_clamp(val_out, 0.0, 1.0); /* clamp */
val_out = cs_gamma(val_out, (MATFLOAT *)cs_get_gamma(gamma_type_dst), EGD_LIN_2_NONLIN); /* regamma */
return val_out;
}
int cs_xy_to_cct(MATFLOAT xy[2])
{ /* McCamy�s polynomial formula for CCT */
MATFLOAT val = (xy[0] - 0.3320) / (xy[1] - 0.1858);
MATFLOAT val2 = val * val;
MATFLOAT val3 = val * val2;
MATFLOAT cct = -449.0 * val3 + 3525.0 * val2 - 6823.0 * val + 5520.33;
return MAT_ROUND(cct);
}
void cs_cct_to_xy(int cct, MATFLOAT xy[2])
{
int val = MAT_CLAMP(cct, CS_CCT_MIN, CS_CCT_MAX) - CS_CCT_MIN;
int vec_ind[2];
MATFLOAT phase;
MATFLOAT vec_x[2], vec_y[2];
vec_ind[0] = val / CS_CCT_INC;
vec_ind[1] = MAT_MIN(vec_ind[0] + 1, CS_CCT_SIZE - 1);
phase = (MATFLOAT)(val - vec_ind[0] * CS_CCT_INC) / (MATFLOAT)CS_CCT_INC;
vec_x[0] = cs_vec_cct_xy[2 * vec_ind[0] + 0];
vec_x[1] = cs_vec_cct_xy[2 * vec_ind[1] + 0];
vec_y[0] = cs_vec_cct_xy[2 * vec_ind[0] + 1];
vec_y[1] = cs_vec_cct_xy[2 * vec_ind[1] + 1];
xy[0] = mat_linear(vec_x, phase);
xy[1] = mat_linear(vec_y, phase);
}
void cs_csc(struct s_color_space *ptr_cs_src, struct s_color_space *ptr_cs_dst,
MATFLOAT rgb_inp[3], MATFLOAT rgb_out[3], int en_chad)
{
MATFLOAT rgb_tmp[3];
MATFLOAT mat_remap[3][3];
cs_genmat_rgb_to_rgb(ptr_cs_src->rgbw_xy, ptr_cs_dst->rgbw_xy, mat_remap, en_chad);
cs_nlin_to_lin_rgb(ptr_cs_src, rgb_inp, rgb_tmp);
mat_eval_3x3(mat_remap, rgb_tmp, rgb_out);
cs_clamp_rgb(rgb_out, 0.0, 1.0);
cs_lin_to_nlin_rgb(ptr_cs_dst, rgb_out, rgb_out);
}
int cs_is_space(struct s_color_space *ptr_color_space,
enum cs_color_space_type color_space_type, enum cs_gamma_type gamma_type)
{
return ((ptr_color_space->color_space_type == color_space_type) &&
(ptr_color_space->gamma_type == gamma_type)) ? 1 : 0;
}
void cs_init_type(MATFLOAT luminance_limits[2],
enum cs_color_space_type color_space_type, enum cs_gamma_type gamma_type,
struct s_color_space *ptr_color_space)
{
struct s_cs_opts cs_opts = {0};
cs_opts.color_space_type = color_space_type;
cs_opts.gamma_type = gamma_type;
cs_opts.mode = 0;
cs_opts.pq_norm = 0.0;
cs_opts.luminance_limits[0] = luminance_limits[0];
cs_opts.luminance_limits[1] = luminance_limits[1];
cs_init(&cs_opts, ptr_color_space);
}
void cs_init_BT709(MATFLOAT luminance_limits[2], struct s_color_space *ptr_color_space)
{
cs_init_type(luminance_limits, ECST_709, EGT_709, ptr_color_space);
}
void cs_init_BT2100(MATFLOAT luminance_limits[2], struct s_color_space *ptr_color_space)
{
cs_init_type(luminance_limits, ECST_BT2020, EGT_PQ, ptr_color_space);
}
void cs_rgb_to_ycbcr2020(MATFLOAT rgb_inp[3], MATFLOAT ycbcr_out[3])
{ /* ITU-R BT.2020 */
ycbcr_out[0] = 0.2627 * rgb_inp[0] + 0.678 * rgb_inp[1] + 0.0593 * rgb_inp[2];
ycbcr_out[1] = (rgb_inp[2] - ycbcr_out[0]) / 1.8814;
ycbcr_out[2] = (rgb_inp[0] - ycbcr_out[0]) / 1.4746;
}
/* gamma = 1.2 - for reference display (1000 cd/m^2) and reference ambient light (5 cd/m^2) */
/* luminance_peak in a range [0,1] normilized to [0,10000]cd/m^2 in linear space */
MATFLOAT cs_ootf_gamma_peak(MATFLOAT gamma, MATFLOAT luminance_peak)
{ /* gamma correction for peak luminance of the display */
return gamma * mat_pow(1.111, mat_log2(luminance_peak / 0.1)); /* normzlized to 1000 nits */
}
/* gamma = 1.2 - for reference display (1000 cd/m^2) and reference ambient light (5 cd/m^2) */
/* luminance_ambient in a range [0,1] normalized to [0,10000]cd/m^2 in linear space - ambient light in linear space */
MATFLOAT cs_ootf_gamma_amb(MATFLOAT gamma, MATFLOAT luminance_ambient)
{ /* gamma correction for ambient light */
return gamma * mat_pow(0.98, mat_log2(luminance_ambient / 0.0005)); /* normalized to 5 nits */
}
MATFLOAT cs_gamma_adjust_sdr(MATFLOAT gamma, MATFLOAT luminance_peak)
{
/* gamma correction for peak luminance of the display */
if (luminance_peak <= 0.1)
gamma = gamma * mat_pow(1.111, mat_log2(luminance_peak / 0.01));
else if ((luminance_peak > 0.1) && (luminance_peak < 0.2))
gamma = gamma + ((luminance_peak > 0.1) ? 0.42 * mat_log10(luminance_peak / 0.1) : 0.0);
else
gamma = gamma * mat_pow(1.111, mat_log2(luminance_peak / 0.1));
return gamma;
}
void cs_chad_gains(MATFLOAT rgbw_xy[8], MATFLOAT w_xy[2], MATFLOAT rgb_gain[3])
{
MATFLOAT rgb_white[3] = { 1.0, 1.0, 1.0 };
MATFLOAT max_gain = 0.0;
MATFLOAT mat_rgb2xyz[3][3], mat_xyz2rgb[3][3];
MATFLOAT mat_chad[3][3];
MATFLOAT xyz_inp[3], xyz_out[3];
int nc;
/* generate RGB to XYZ and back transformation matrixes */
cs_genmat_rgb_to_xyz(rgbw_xy, mat_rgb2xyz);
mat_inv3x3(mat_rgb2xyz, mat_xyz2rgb);
/* generate matrix of white point conversion from display to target */
cs_genmat_chad(&rgbw_xy[6], w_xy, mat_chad);
/* map white to gains */
mat_eval_3x3(mat_rgb2xyz, rgb_white, xyz_inp);
mat_eval_3x3(mat_chad, xyz_inp, xyz_out);
mat_eval_3x3(mat_xyz2rgb, xyz_out, rgb_gain);
/* normalize gains to max */
for (nc = 0; nc < 3; nc++)
max_gain = MAT_MAX(max_gain, rgb_gain[nc]);
for (nc = 0; nc < 3; nc++)
rgb_gain[nc] = rgb_gain[nc] / max_gain;
}
void cs_genmat_cct(struct s_color_space *ptr_cs, int cct_shift, int norm, MATFLOAT mat_cct[3][3])
{
MATFLOAT xy[2];
MATFLOAT mat_chad[3][3];
MATFLOAT mat_tmp[3][3];
cs_cct_to_xy(ptr_cs->cct + cct_shift, xy);
cs_genmat_chad(&ptr_cs->rgbw_xy[6], xy, mat_chad);
mat_mul3x3(mat_chad, ptr_cs->mat_rgb2xyz, mat_tmp);
mat_mul3x3(ptr_cs->mat_xyz2rgb, mat_tmp, mat_cct);
if (norm) {
MATFLOAT rgb_white[3] = { 1.0, 1.0, 1.0 };
MATFLOAT max_gain = 0.0;
MATFLOAT rgb_gain[3];
int nc, ni;
mat_eval_3x3(mat_cct, rgb_white, rgb_gain);
for (nc = 0; nc < 3; nc++)
max_gain = MAT_MAX(max_gain, rgb_gain[nc]);
for (nc = 0; nc < 3; nc++)
for (ni = 0; ni < 3; ni++)
mat_cct[nc][ni] = mat_cct[nc][ni] / max_gain;
}
}
int cs_rgb_to_vsh(MATFLOAT rgb[3], MATFLOAT vsh[3])
{
MATFLOAT r = rgb[0];
MATFLOAT g = rgb[1];
MATFLOAT b = rgb[2];
MATFLOAT val_min, val_max, delta;
val_max = (g > b) ? g : b;
if (r > val_max)
val_max = r;
val_min = (g < b) ? g : b;
if (r < val_min)
val_min = r;
vsh[0] = val_max;
delta = val_max - val_min;
if ((val_max != 0.0) && (delta != 0.0))
vsh[1] = delta / val_max;
else {
vsh[2] = 0.0;
vsh[1] = 0.0;
return 1;
}
if (r == val_max)
vsh[2] = (g - b) / delta;
else if (g == val_max)
vsh[2] = 2.0 + (b - r) / delta;
else
vsh[2] = 4.0 + (r - g) / delta;
vsh[2] = vsh[2] * mat_get_pi() / 3.0;
vsh[2] = mat_norm_angle(vsh[2]); /* [0.0, 2PI) */
return 0;
}
void cs_vsh_to_rgb(MATFLOAT vsh[3], MATFLOAT rgb[3])
{
MATFLOAT v = vsh[0];
MATFLOAT s = vsh[1];
MATFLOAT r = v;
MATFLOAT g = v;
MATFLOAT b = v;
if (s > 0.0) {
MATFLOAT h = 3.0 * vsh[2] / mat_get_pi();
int ni = MAT_CLAMP((int)h, 0, 5);
MATFLOAT f = h - (MATFLOAT)ni;
MATFLOAT p = v * (1.0 - s);
MATFLOAT q = v * (1.0 - s * f);
MATFLOAT t = v * (1.0 - s * (1.0 - f));
switch (ni) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
case 5:
r = v;
g = p;
b = q;
break;
}
}
rgb[0] = r;
rgb[1] = g;
rgb[2] = b;
}
/* YUV functions */
void cs_yuv_to_ysh(MATFLOAT yuv_inp[3], MATFLOAT ysh_out[3])
{
ysh_out[0] = yuv_inp[0];
ysh_out[1] = mat_radius(yuv_inp[2] - 0.5, yuv_inp[1] - 0.5);
ysh_out[2] = mat_angle(yuv_inp[2] - 0.5, yuv_inp[1] - 0.5);
}
void cs_ysh_to_yuv(MATFLOAT ysh_inp[3], MATFLOAT yuv_out[3])
{
yuv_out[0] = ysh_inp[0];
yuv_out[1] = ysh_inp[1] * mat_cos(ysh_inp[2]) + 0.5;
yuv_out[2] = ysh_inp[1] * mat_sin(ysh_inp[2]) + 0.5;
}
/* CIE LAB functions */
void cs_rgb_to_lab(MATFLOAT rgb[3], MATFLOAT lab[3], struct s_color_space *ptr_color_space)
{
MATFLOAT xyz[3];
cs_gamma_rgb(rgb, rgb, ptr_color_space->gamma_parm, EGD_NONLIN_2_LIN);
mat_eval_3x3(ptr_color_space->mat_rgb2xyz, rgb, xyz);
cs_xyz_to_lab(xyz, lab, ptr_color_space->white_xyz);
}
void cs_lab_to_rgb(MATFLOAT lab[3], MATFLOAT rgb[3], struct s_color_space *ptr_color_space)
{
MATFLOAT xyz[3];
cs_lab_to_xyz(lab, xyz, ptr_color_space->white_xyz);
mat_eval_3x3(ptr_color_space->mat_xyz2rgb, xyz, rgb);
cs_clip_rgb(rgb, 0.0, 1.0);
cs_gamma_rgb(rgb, rgb, ptr_color_space->gamma_parm, EGD_LIN_2_NONLIN);
}
void cs_xyz_to_lab(MATFLOAT xyz[3], MATFLOAT lab[3], MATFLOAT white_xyz[3])
{
int nc;
MATFLOAT f[3], ft;
for (nc = 0; nc < 3; nc++) {
ft = xyz[nc] / white_xyz[nc];
f[nc] = (ft > CS_LAB_E) ? mat_pow(ft, 1.0 / 3.0) : (CS_LAB_K * ft + 16.0) / 116.0;
}
lab[0] = 116.0f * f[1] - 16.0;
lab[1] = 500.0f * (f[0] - f[1]);
lab[2] = 200.0f * (f[1] - f[2]);
}
void cs_lab_to_xyz(MATFLOAT lab[3], MATFLOAT xyz[3], MATFLOAT white_xyz[3])
{
int nc;
MATFLOAT f[3];
MATFLOAT ft = (lab[0] + 16.0) / 116.0;
f[0] = ft + lab[1] / 500.0;
f[1] = ft;
f[2] = ft - lab[2] / 200.0;
xyz[0] = mat_pow(f[0], 3.0);
if (xyz[0] <= CS_LAB_E)
xyz[0] = (116.0 * f[0] - 16.0) / CS_LAB_K;
if (lab[0] > CS_LAB_K * CS_LAB_E)
xyz[1] = mat_pow((lab[0] + 16.0) / 116.0, 3.0);
else
xyz[1] = lab[0] / CS_LAB_K;
xyz[2] = mat_pow(f[2], 3.0);
if (xyz[2] <= CS_LAB_E)
xyz[2] = (116.0 * f[2] - 16.0) / CS_LAB_K;
for (nc = 0; nc < 3; nc++)
xyz[nc] *= white_xyz[nc];
}
MATFLOAT cs_de94(MATFLOAT lab0[3], MATFLOAT lab1[3])
{
static const MATFLOAT Kc = 1.0;
static const MATFLOAT Kh = 1.0;
static const MATFLOAT Kl = 1.0;
static const MATFLOAT K1 = 0.045;
static const MATFLOAT K2 = 0.015;
MATFLOAT dL = lab0[0] - lab1[0];
MATFLOAT C1 = mat_sqrt(lab0[1] * lab0[1] + lab0[2] * lab0[2]);
MATFLOAT C2 = mat_sqrt(lab1[1] * lab1[1] + lab1[2] * lab1[2]);
MATFLOAT dC = C1 - C2;
MATFLOAT da = lab0[1] - lab1[1];
MATFLOAT db = lab0[2] - lab1[2];
MATFLOAT tmp = da * da + db * db - dC * dC;
MATFLOAT dH = (tmp > 0) ? mat_sqrt(tmp) : 0.0;
MATFLOAT Sl = 1.0;
MATFLOAT Sc = 1.0 + K1 * C1;
MATFLOAT Sh = 1.0 + K2 * C1;
dL /= (Kl * Sl);
dC /= (Kc * Sc);
dH /= (Kh * Sh);
return mat_sqrt(dL * dL + dC * dC + dH * dH);
}
/* gamma = 1.2 - for reference display (1000 cd/m^2) and reference ambient light (5 cd/m^2) */
/* luminance_peak in a range [0,1] normilized to [0,10000]cd/m^2 in linear space */
/* luminance_amb in a range [0,1] normalized to [0,10000]cd/m^2 in linear space - ambient light in linear space */
MATFLOAT cs_gamma_adjust(MATFLOAT gamma, MATFLOAT luminance_peak, MATFLOAT luminance_amb)
{
/* gamma correction for peak luminance of the display */
if (luminance_peak < 0.2)
gamma = gamma + ((luminance_peak > 0.1) ? 0.42 * mat_log10(luminance_peak / 0.1) : 0.0);
else
gamma = gamma * mat_pow(1.111, mat_log2(luminance_peak / 0.1));
/* gamma correction for ambient light */
gamma = gamma - 0.076 * mat_log10(luminance_amb / 5.0);
return gamma;
}
/* BT.2100 */
/* input must be in arange [0,1] normilized to [0,Lw]cd/m^2 in linear or non-linear space */
/* output must be in a range [0,1] normilized to [0,Lw]cd/m^2 in linear or non-linear space */
MATFLOAT cs_gamma_hlg(MATFLOAT val, enum cs_gamma_dir gamma_dir)
{
static const MATFLOAT s_a = 0.17883277;
static const MATFLOAT s_b = 0.28466892;
static const MATFLOAT s_c = 0.55991073;
MATFLOAT val_out;
if (gamma_dir == EGD_LIN_2_NONLIN)
val_out = (val <= (1.0 / 12.0)) ? mat_sqrt(3.0 * val) : s_a * mat_log(12.0 * val - s_b) + s_c;
else
val_out = (val <= 0.5) ? val * val / 3.0 : (mat_exp((val - s_c) / s_a) + s_b) / 12.0;
return MAT_CLAMP(val_out, 0.0, 1.0);
}
/* HLG OOTF */
/* rgb_inp[] in a range [0,1] normalized to [0,Lw]cd/m^2 in linear space */
/* rgb_out[] in a range [0,1] normalized to [0,10000]cd/m^2 in linear space */
/* luminance_peak in a range [0,1] normalized to [0,10000]cd/m^2 in linear space - mastering Lb and Lw */
/* system_gamma = 1.2 - for reference display (1000 cd/m^2) and reference ambient light (5 cd/m^2) */
void cs_hlg_ootf(MATFLOAT rgb_inp[3], MATFLOAT rgb_out[3], MATFLOAT luminance_peak, MATFLOAT system_gamma)
{ /* output may be the same as input */
MATFLOAT ys = 0.2627 * rgb_inp[0] + 0.6780 * rgb_inp[1] + 0.0593 * rgb_inp[2];
MATFLOAT scale = mat_pow(ys, system_gamma - 1.0);
int nc;
for (nc = 0; nc < 3; nc++) {
rgb_out[nc] = rgb_inp[nc] * scale * luminance_peak;
rgb_out[nc] = MAT_CLAMP(rgb_out[nc], 0.0, 1.0);
}
}
/* HLG OOTF_INV */
/* rgb_inp[] in a range [0,1] normalized to [0,10000]cd/m^2 in linear space */
/* rgb_out[] in a range [0,1] normalized to [0,Lw]cd/m^2 in linear space */
/* luminance_peak in a range [0,1] normalized to [0,10000]cd/m^2 in linear space - mastering Lb and Lw */
/* system_gamma = 1.2 - for reference display (1000 cd/m^2) and reference ambient light (5 cd/m^2) */
void cs_hlg_ootf_inv(MATFLOAT rgb_inp[3], MATFLOAT rgb_out[3], MATFLOAT luminance_peak, MATFLOAT system_gamma)
{ /* output may be the same as input */
MATFLOAT yd = (0.2627 * rgb_inp[0] + 0.6780 * rgb_inp[1] + 0.0593 * rgb_inp[2]) / luminance_peak;
MATFLOAT scale = mat_pow(yd, (1.0 - system_gamma) / system_gamma) / luminance_peak;
int nc;
for (nc = 0; nc < 3; nc++) {
rgb_out[nc] = rgb_inp[nc] * scale;
rgb_out[nc] = MAT_CLAMP(rgb_out[nc], 0.0, 1.0);
}
}
/* HLG OETF */
/* rgb_inp[] in a range [0,1] normalized to [0,Lw]cd/m^2 in linear space */
/* rgb_out[] in a range [0,1] normalized to [0,Lw]cd/m^2 in non-linear space */
/* luminance_peak in a range [0,1] normalized to [0,10000]cd/m^2 in linear space - mastering Lb and Lw */
/* system_gamma = 1.2 - for reference display (1000 cd/m^2) and reference ambient light (5 cd/m^2) */
void cs_hlg_oetf(MATFLOAT rgb_inp[3], MATFLOAT rgb_out[3], MATFLOAT luminance_peak, MATFLOAT system_gamma)
{ /* output may be the same as input */
int nc;
cs_hlg_ootf_inv(rgb_inp, rgb_out, luminance_peak, system_gamma);
for (nc = 0; nc < 3; nc++)
rgb_out[nc] = cs_gamma_hlg(rgb_out[nc], EGD_LIN_2_NONLIN);
}
/* HLG EOTF */
/* rgb_inp[] in a range [0,1] normalized to [0,Lw]cd/m^2 in non-linear space */
/* rgb_out[] in a range [0,1] normalized to [0,Lw]cd/m^2 in linear space */
/* vec_luminace in a range [0,1] normalized to [0,10000]cd/m^2 in linear space - mastering Lb and Lw */
/* system_gamma = 1.2 - for reference display (1000 cd/m^2) and reference ambient light (5 cd/m^2) */
/* beta - user black level lift (= 0.0) */
void cs_hlg_eotf(MATFLOAT rgb_inp[3], MATFLOAT rgb_out[3], MATFLOAT luminance_limits[3],
MATFLOAT system_gamma, MATFLOAT beta)
{ /* output may be the same as input */
int nc;
for (nc = 0; nc < 3; nc++) {
rgb_out[nc] = MAT_MAX((1.0 - beta) * rgb_inp[nc] + beta, 0.0);
rgb_out[nc] = cs_gamma_hlg(rgb_out[nc], EGD_NONLIN_2_LIN);
}
cs_hlg_ootf(rgb_out, rgb_out, luminance_limits[1], system_gamma);
}
/* HLG system gamma calculation */
/* peak_luminance - Lw */
MATFLOAT cs_hlg_system_gamma(MATFLOAT peak_luminance)
{
MATFLOAT norm_peak = peak_luminance / (1000.0 / CS_MAX_LUMINANCE);
MATFLOAT system_gamma;
if ((peak_luminance < 400.0 / CS_MAX_LUMINANCE) || (peak_luminance > 2000.0 / CS_MAX_LUMINANCE))
system_gamma = 1.2 * mat_pow(1.111, mat_log2(norm_peak));
else
system_gamma = 1.2 + 0.42 * mat_log10(norm_peak);
return system_gamma;
}
#if 0
/* PQ to HLG Transcode */
/* rgb_inp[] in a range [0,1] normalized to [0,10000]cd/m^2 in non-linear space */
/* rgb_out[] in a range [0,1] normalized to [0,Lw]cd/m^2 in non-linear space */
/* luminance_peak in a range [0,1] normalized to [0,10000]cd/m^2 in linear space - mastering Lb and Lw */
/* gamma = 1.2 - for reference display (1000 cd/m^2) and reference ambient light (5 cd/m^2) */
void cs_pq_to_hlg(MATFLOAT rgb_inp[3], MATFLOAT rgb_out[3], MATFLOAT luminance_peak, MATFLOAT gamma)
{
MATFLOAT rgb_lin[3];
int nc;
for (nc = 0; nc < 3; nc++)
rgb_lin[nc] = cs_gamma_pq(rgb_inp[nc], EGD_NONLIN_2_LIN); /* PQ to Linear [0,10000]->[0,10000] */
cs_hlg_ootf_inv(rgb_lin, rgb_lin, luminance_peak, gamma); /* OOTF-1 - [0,10000]->[0,Lw] */
cs_hlg_oetf(rgb_lin, rgb_out, luminance_peak, gamma); /* Linear to HLG - [0,Lw]->[0,Lw] */
}
/* HLG to PQ Transcode */
/* rgb_inp[] in a range [0,1] normalized to [0,Lw]cd/m^2 in non-linear space */
/* rgb_out[] in a range [0,1] normalized to [0,10000]cd/m^2 in non-linear space */
/* vec_luminace in a range [0,1] normalized to [0,10000]cd/m^2 in linear space - mastering Lb and Lw */
/* gamma = 1.2 - for reference display (1000 cd/m^2) and reference ambient light (5 cd/m^2) */
void cs_hlg_to_pq(MATFLOAT rgb_inp[3], MATFLOAT rgb_out[3], MATFLOAT vec_luminance[3], MATFLOAT gamma)
{
MATFLOAT rgb_lin[3];
int nc;
cs_hlg_eotf(rgb_inp, rgb_lin, vec_luminance, gamma); /* HLG to Linear - [0,Lw]->[0,Lw] */
cs_hlg_ootf(rgb_lin, rgb_lin, vec_luminance[1], gamma); /* OOTF - [0,Lw]->[0,10000] */
for (nc = 0; nc < 3; nc++)
rgb_out[nc] = cs_gamma_pq(rgb_lin[nc], EGD_LIN_2_NONLIN); /* Linear to PQ [0,10000]->[0,1000] */
}
/* BT.2390 display referred simplified */
/* rgb_inp[] in a range [0,1] normalized to [0,100]cd/m^2 in non-linear space */
/* rgb_out[] in a range [0,1] normalized to [0,1000]cd/m^2 in non-linear space */
void cs_sdr_to_hlg(MATFLOAT rgb_inp[3], MATFLOAT rgb_out[3], MATFLOAT en_709_2020)
{
MATFLOAT sdr_lb = 0.0;
MATFLOAT sdr_lw = 100.0 / 10000.0;
MATFLOAT sdr_gamma = 2.4;
MATFLOAT scale = 0.2546; /* 0.75HLG = 392cd/m^2 */
MATFLOAT hlg_lw = 1000.0 / 10000.0;
MATFLOAT hlg_amb = 5.0 / 10000.0;
MATFLOAT hlg_gamma = cs_gamma_adjust(1.2, hlg_lw, hlg_amb);
MATFLOAT gamma = 1.03;
MATFLOAT rgb_lin[3];
int nc;
for (nc = 0; nc < 3; nc++) {
rgb_lin[nc] = cs_gamma_1886(rgb_inp[nc], sdr_lb, sdr_lw, sdr_gamma); /* [0,10000]cd/m^2 */
rgb_lin[nc] = rgb_lin[nc] / sdr_lw; /* [0,sdr_lw]cd/m^2 */
rgb_lin[nc] = MAT_CLAMP(rgb_lin[nc], 0.0, 1.0);
}
if (en_709_2020) {
MATFLOAT rgb_tmp[3];
mat_copy(rgb_lin, rgb_tmp, 3);
mat_eval_3x3(cs_mat_709_2020, rgb_tmp, rgb_lin); /* [0,sdr_lw]cd/m^2 */
}
for (nc = 0; nc < 3; nc++) {
rgb_lin[nc] = rgb_lin[nc] * scale; /* scale to 392cd/m^2 [0,hlg_lw] */
rgb_lin[nc] = mat_pow(rgb_lin[nc], 1.0 / gamma); /* [0,hlg_lw] */
}
cs_hlg_oetf(rgb_lin, rgb_out, hlg_lw, hlg_gamma); /* Linear to HLG - [0,hlg_lw]cd/m^2->[0,hlg_lw]cd/m^2 */
}
#endif