1 #include <cstring>
2 #include "common/except.h"
3 #include "colorspace.h"
4 #include "colorspace_param.h"
5 #include "matrix3.h"
6 
7 namespace zimg {
8 namespace colorspace {
9 
10 namespace {
11 
get_yuv_constants(double * kr,double * kb,MatrixCoefficients matrix)12 void get_yuv_constants(double *kr, double *kb, MatrixCoefficients matrix)
13 {
14 	switch (matrix) {
15 	case MatrixCoefficients::RGB:
16 		*kr = 0;
17 		*kb = 0;
18 		break;
19 	case MatrixCoefficients::FCC:
20 		*kr = FCC_KR;
21 		*kb = FCC_KB;
22 		break;
23 	case MatrixCoefficients::SMPTE_240M:
24 		*kr = SMPTE_240M_KR;
25 		*kb = SMPTE_240M_KB;
26 		break;
27 	case MatrixCoefficients::REC_601:
28 		*kr = REC_601_KR;
29 		*kb = REC_601_KB;
30 		break;
31 	case MatrixCoefficients::REC_709:
32 		*kr = REC_709_KR;
33 		*kb = REC_709_KB;
34 		break;
35 	case MatrixCoefficients::REC_2020_NCL:
36 	case MatrixCoefficients::REC_2020_CL:
37 		*kr = REC_2020_KR;
38 		*kb = REC_2020_KB;
39 		break;
40 	default:
41 		error::throw_<error::InternalError>("unrecognized matrix coefficients");
42 	}
43 }
44 
xy_to_xyz(double x,double y)45 Vector3 xy_to_xyz(double x, double y)
46 {
47 	Vector3 ret;
48 
49 	ret[0] = x / y;
50 	ret[1] = 1.0;
51 	ret[2] = (1.0 - x - y) / y;
52 
53 	return ret;
54 }
55 
get_white_point(ColorPrimaries primaries)56 Vector3 get_white_point(ColorPrimaries primaries)
57 {
58 	switch (primaries) {
59 	case ColorPrimaries::REC_470_M:
60 	case ColorPrimaries::FILM:
61 		return xy_to_xyz(ILLUMINANT_C[0], ILLUMINANT_C[1]);
62 	case ColorPrimaries::XYZ:
63 		return xy_to_xyz(ILLUMINANT_E[0], ILLUMINANT_E[1]);
64 	case ColorPrimaries::DCI_P3:
65 		return xy_to_xyz(ILLUMINANT_DCI[0], ILLUMINANT_DCI[1]);
66 	default:
67 		return xy_to_xyz(ILLUMINANT_D65[0], ILLUMINANT_D65[1]);
68 	}
69 }
70 
get_primaries_xy(double out[3][2],ColorPrimaries primaries)71 void get_primaries_xy(double out[3][2], ColorPrimaries primaries)
72 {
73 	switch (primaries) {
74 	case ColorPrimaries::REC_470_M:
75 		memcpy(out, REC_470_M_PRIMARIES, sizeof(REC_470_M_PRIMARIES));
76 		break;
77 	case ColorPrimaries::REC_470_BG:
78 		memcpy(out, REC_470_BG_PRIMARIES, sizeof(REC_470_BG_PRIMARIES));
79 		break;
80 	case ColorPrimaries::SMPTE_C:
81 		memcpy(out, SMPTE_C_PRIMARIES, sizeof(SMPTE_C_PRIMARIES));
82 		break;
83 	case ColorPrimaries::REC_709:
84 		memcpy(out, REC_709_PRIMARIES, sizeof(REC_709_PRIMARIES));
85 		break;
86 	case ColorPrimaries::FILM:
87 		memcpy(out, FILM_PRIMARIES, sizeof(FILM_PRIMARIES));
88 		break;
89 	case ColorPrimaries::REC_2020:
90 		memcpy(out, REC_2020_PRIMARIES, sizeof(REC_2020_PRIMARIES));
91 		break;
92 	case ColorPrimaries::DCI_P3:
93 	case ColorPrimaries::DCI_P3_D65:
94 		memcpy(out, DCI_P3_PRIMARIES, sizeof(DCI_P3_PRIMARIES));
95 		break;
96 	case ColorPrimaries::JEDEC_P22:
97 		memcpy(out, JEDEC_P22_PRIMARIES, sizeof(JEDEC_P22_PRIMARIES));
98 		break;
99 	default:
100 		error::throw_<error::InternalError>("unrecognized primaries");
101 	}
102 }
103 
get_primaries_xyz(ColorPrimaries primaries)104 Matrix3x3 get_primaries_xyz(ColorPrimaries primaries)
105 {
106 	// Columns: R G B
107 	// Rows: X Y Z
108 	Matrix3x3 ret;
109 	double primaries_xy[3][2];
110 
111 	get_primaries_xy(primaries_xy, primaries);
112 
113 	ret[0] = xy_to_xyz(primaries_xy[0][0], primaries_xy[0][1]);
114 	ret[1] = xy_to_xyz(primaries_xy[1][0], primaries_xy[1][1]);
115 	ret[2] = xy_to_xyz(primaries_xy[2][0], primaries_xy[2][1]);
116 
117 	return transpose(ret);
118 }
119 
get_yuv_constants_from_primaries(double * kr,double * kb,ColorPrimaries primaries)120 void get_yuv_constants_from_primaries(double *kr, double *kb, ColorPrimaries primaries)
121 {
122 	// ITU-T H.265 Annex E, Eq (E-22) to (E-27).
123 	double primaries_xy[3][2];
124 	get_primaries_xy(primaries_xy, primaries);
125 
126 	Vector3 r_xyz = xy_to_xyz(primaries_xy[0][0], primaries_xy[0][1]);
127 	Vector3 g_xyz = xy_to_xyz(primaries_xy[1][0], primaries_xy[1][1]);
128 	Vector3 b_xyz = xy_to_xyz(primaries_xy[2][0], primaries_xy[2][1]);
129 	Vector3 white_xyz = get_white_point(primaries);
130 
131 	Vector3 x_rgb = { r_xyz[0], g_xyz[0], b_xyz[0] };
132 	Vector3 y_rgb = { r_xyz[1], g_xyz[1], b_xyz[1] };
133 	Vector3 z_rgb = { r_xyz[2], g_xyz[2], b_xyz[2] };
134 
135 	*kr = dot(white_xyz, cross(g_xyz, b_xyz)) / dot(x_rgb, cross(y_rgb, z_rgb));
136 	*kb = dot(white_xyz, cross(r_xyz, g_xyz)) / dot(x_rgb, cross(y_rgb, z_rgb));
137 }
138 
ncl_rgb_to_yuv_matrix_from_kr_kb(double kr,double kb)139 Matrix3x3 ncl_rgb_to_yuv_matrix_from_kr_kb(double kr, double kb)
140 {
141 	Matrix3x3 ret;
142 	double kg = 1.0 - kr - kb;
143 	double uscale;
144 	double vscale;
145 
146 	uscale = 1.0 / (2.0 - 2.0 * kb);
147 	vscale = 1.0 / (2.0 - 2.0 * kr);
148 
149 	ret[0][0] = kr;
150 	ret[0][1] = kg;
151 	ret[0][2] = kb;
152 
153 	ret[1][0] = -kr * uscale;
154 	ret[1][1] = -kg * uscale;
155 	ret[1][2] = (1.0 - kb) * uscale;
156 
157 	ret[2][0] = (1.0 - kr) * vscale;
158 	ret[2][1] = -kg * vscale;
159 	ret[2][2] = -kb * vscale;
160 
161 	return ret;
162 }
163 
164 } // namespace
165 
166 
ncl_yuv_to_rgb_matrix(MatrixCoefficients matrix)167 Matrix3x3 ncl_yuv_to_rgb_matrix(MatrixCoefficients matrix)
168 {
169 	return inverse(ncl_rgb_to_yuv_matrix(matrix));
170 }
171 
ncl_rgb_to_yuv_matrix(MatrixCoefficients matrix)172 Matrix3x3 ncl_rgb_to_yuv_matrix(MatrixCoefficients matrix)
173 {
174 	double kr, kb;
175 
176 	switch (matrix)
177 	{
178 	case MatrixCoefficients::YCGCO:
179 		return {
180 			{  0.25, 0.5,  0.25 },
181 			{ -0.25, 0.5, -0.25 },
182 			{  0.5,  0,   -0.5 }
183 		};
184 	case MatrixCoefficients::REC_2100_LMS:
185 		return {
186 			{ 1688.0 / 4096.0, 2146.0 / 4096.0,  262.0 / 4096.0 },
187 			{  683.0 / 4096.0, 2951.0 / 4096.0,  462.0 / 4096.0 },
188 			{   99.0 / 4096.0,  309.0 / 4096.0, 3688.0 / 4096.0 }
189 		};
190 	default:
191 		get_yuv_constants(&kr, &kb, matrix);
192 		return ncl_rgb_to_yuv_matrix_from_kr_kb(kr, kb);
193 	}
194 }
195 
ncl_yuv_to_rgb_matrix_from_primaries(ColorPrimaries primaries)196 Matrix3x3 ncl_yuv_to_rgb_matrix_from_primaries(ColorPrimaries primaries)
197 {
198 	double kr, kb;
199 
200 	switch (primaries) {
201 	case ColorPrimaries::REC_709:
202 		return ncl_yuv_to_rgb_matrix(MatrixCoefficients::REC_709);
203 	case ColorPrimaries::REC_2020:
204 		return ncl_yuv_to_rgb_matrix(MatrixCoefficients::REC_2020_NCL);
205 	default:
206 		get_yuv_constants_from_primaries(&kr, &kb, primaries);
207 		return inverse(ncl_rgb_to_yuv_matrix_from_kr_kb(kr, kb));
208 	}
209 }
210 
ncl_rgb_to_yuv_matrix_from_primaries(ColorPrimaries primaries)211 Matrix3x3 ncl_rgb_to_yuv_matrix_from_primaries(ColorPrimaries primaries)
212 {
213 	double kr, kb;
214 
215 	switch (primaries) {
216 	case ColorPrimaries::REC_709:
217 		return ncl_rgb_to_yuv_matrix(MatrixCoefficients::REC_709);
218 	case ColorPrimaries::REC_2020:
219 		return ncl_rgb_to_yuv_matrix(MatrixCoefficients::REC_2020_NCL);
220 	default:
221 		get_yuv_constants_from_primaries(&kr, &kb, primaries);
222 		return ncl_rgb_to_yuv_matrix_from_kr_kb(kr, kb);
223 	}
224 }
225 
ictcp_to_lms_matrix(TransferCharacteristics transfer)226 Matrix3x3 ictcp_to_lms_matrix(TransferCharacteristics transfer)
227 {
228 	return inverse(lms_to_ictcp_matrix(transfer));
229 }
230 
lms_to_ictcp_matrix(TransferCharacteristics transfer)231 Matrix3x3 lms_to_ictcp_matrix(TransferCharacteristics transfer)
232 {
233 	if (transfer == TransferCharacteristics::ARIB_B67) {
234 		return{
235 			{              0.5,               0.5,             0.0 },
236 			{  3625.0 / 4096.0,  -7465.0 / 4096.0, 3840.0 / 4096.0 },
237 			{  9500.0 / 4096.0,  -9212.0 / 4096.0, -288.0 / 4096.0 }
238 		};
239 	} else {
240 		return {
241 			{              0.5,               0.5,             0.0 },
242 			{  6610.0 / 4096.0, -13613.0 / 4096.0, 7003.0 / 4096.0 },
243 			{ 17933.0 / 4096.0, -17390.0 / 4096.0, -543.0 / 4096.0 }
244 		};
245 	}
246 }
247 
248 // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
gamut_rgb_to_xyz_matrix(ColorPrimaries primaries)249 Matrix3x3 gamut_rgb_to_xyz_matrix(ColorPrimaries primaries)
250 {
251 	if (primaries == ColorPrimaries::XYZ)
252 		return Matrix3x3::identity();
253 
254 	Matrix3x3 xyz_matrix = get_primaries_xyz(primaries);
255 	Vector3 white_xyz = get_white_point(primaries);
256 
257 	Vector3 s = inverse(xyz_matrix) * white_xyz;
258 	Matrix3x3 m = { xyz_matrix[0] * s, xyz_matrix[1] * s, xyz_matrix[2] * s };
259 
260 	return m;
261 }
262 
gamut_xyz_to_rgb_matrix(ColorPrimaries primaries)263 Matrix3x3 gamut_xyz_to_rgb_matrix(ColorPrimaries primaries)
264 {
265 	if (primaries == ColorPrimaries::XYZ)
266 		return Matrix3x3::identity();
267 
268 	return inverse(gamut_rgb_to_xyz_matrix(primaries));
269 }
270 
271 // http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
white_point_adaptation_matrix(ColorPrimaries in,ColorPrimaries out)272 Matrix3x3 white_point_adaptation_matrix(ColorPrimaries in, ColorPrimaries out)
273 {
274 	const Matrix3x3 bradford = {
275 		{  0.8951,  0.2664, -0.1614 },
276 		{ -0.7502,  1.7135,  0.0367 },
277 		{  0.0389, -0.0685,  1.0296 },
278 	};
279 
280 	Vector3 white_in = get_white_point(in);
281 	Vector3 white_out = get_white_point(out);
282 
283 	if (white_in == white_out)
284 		return Matrix3x3::identity();
285 
286 	Vector3 rgb_in = bradford * white_in;
287 	Vector3 rgb_out = bradford * white_out;
288 
289 	Matrix3x3 m{};
290 	m[0][0] = rgb_out[0] / rgb_in[0];
291 	m[1][1] = rgb_out[1] / rgb_in[1];
292 	m[2][2] = rgb_out[2] / rgb_in[2];
293 
294 	return inverse(bradford) * m * bradford;
295 }
296 
297 } // namespace colorspace
298 } // namespace zimg
299