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