Add input support for packed 10-bit Y'CbCr.
[movit] / ycbcr_input_test.cpp
1 // Unit tests for YCbCrInput. Also tests the matrix functions in ycbcr.cpp directly.
2
3 #include <epoxy/gl.h>
4 #include <stddef.h>
5
6 #include <Eigen/Core>
7 #include <Eigen/LU>
8
9 #include "effect_chain.h"
10 #include "gtest/gtest.h"
11 #include "test_util.h"
12 #include "util.h"
13 #include "resource_pool.h"
14 #include "ycbcr_input.h"
15
16 namespace movit {
17
18 TEST(YCbCrInputTest, Simple444) {
19         const int width = 1;
20         const int height = 5;
21
22         // Pure-color test inputs, calculated with the formulas in Rec. 601
23         // section 2.5.4.
24         unsigned char y[width * height] = {
25                 16, 235, 81, 145, 41,
26         };
27         unsigned char cb[width * height] = {
28                 128, 128, 90, 54, 240,
29         };
30         unsigned char cr[width * height] = {
31                 128, 128, 240, 34, 110,
32         };
33         float expected_data[4 * width * height] = {
34                 0.0, 0.0, 0.0, 1.0,
35                 1.0, 1.0, 1.0, 1.0,
36                 1.0, 0.0, 0.0, 1.0,
37                 0.0, 1.0, 0.0, 1.0,
38                 0.0, 0.0, 1.0, 1.0,
39         };
40         float out_data[4 * width * height];
41
42         EffectChainTester tester(NULL, width, height);
43
44         ImageFormat format;
45         format.color_space = COLORSPACE_sRGB;
46         format.gamma_curve = GAMMA_sRGB;
47
48         YCbCrFormat ycbcr_format;
49         ycbcr_format.luma_coefficients = YCBCR_REC_601;
50         ycbcr_format.full_range = false;
51         ycbcr_format.num_levels = 256;
52         ycbcr_format.chroma_subsampling_x = 1;
53         ycbcr_format.chroma_subsampling_y = 1;
54         ycbcr_format.cb_x_position = 0.5f;
55         ycbcr_format.cb_y_position = 0.5f;
56         ycbcr_format.cr_x_position = 0.5f;
57         ycbcr_format.cr_y_position = 0.5f;
58
59         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
60         input->set_pixel_data(0, y);
61         input->set_pixel_data(1, cb);
62         input->set_pixel_data(2, cr);
63         tester.get_chain()->add_input(input);
64
65         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
66
67         // Y'CbCr isn't 100% accurate (the input values are rounded),
68         // so we need some leeway.
69         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
70 }
71
72 TEST(YCbCrInputTest, Interleaved444) {
73         const int width = 1;
74         const int height = 5;
75
76         // Same data as Simple444, just rearranged.
77         unsigned char data[width * height * 3] = {
78                  16, 128, 128,
79                 235, 128, 128,
80                  81,  90, 240,
81                 145,  54,  34,
82                  41, 240, 110,
83         };
84         float expected_data[4 * width * height] = {
85                 0.0, 0.0, 0.0, 1.0,
86                 1.0, 1.0, 1.0, 1.0,
87                 1.0, 0.0, 0.0, 1.0,
88                 0.0, 1.0, 0.0, 1.0,
89                 0.0, 0.0, 1.0, 1.0,
90         };
91         float out_data[4 * width * height];
92
93         EffectChainTester tester(NULL, width, height);
94
95         ImageFormat format;
96         format.color_space = COLORSPACE_sRGB;
97         format.gamma_curve = GAMMA_sRGB;
98
99         YCbCrFormat ycbcr_format;
100         ycbcr_format.luma_coefficients = YCBCR_REC_601;
101         ycbcr_format.full_range = false;
102         ycbcr_format.num_levels = 256;
103         ycbcr_format.chroma_subsampling_x = 1;
104         ycbcr_format.chroma_subsampling_y = 1;
105         ycbcr_format.cb_x_position = 0.5f;
106         ycbcr_format.cb_y_position = 0.5f;
107         ycbcr_format.cr_x_position = 0.5f;
108         ycbcr_format.cr_y_position = 0.5f;
109
110         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_INTERLEAVED);
111         input->set_pixel_data(0, data);
112         tester.get_chain()->add_input(input);
113
114         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
115
116         // Y'CbCr isn't 100% accurate (the input values are rounded),
117         // so we need some leeway.
118         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
119 }
120
121 TEST(YCbCrInputTest, FullRangeRec601) {
122         const int width = 1;
123         const int height = 5;
124
125         // Pure-color test inputs, calculated with the formulas in Rec. 601
126         // section 2.5.4 but without the scaling factors applied
127         // (so both R, G, B, Y, Cb and R vary from 0 to 255).
128         unsigned char y[width * height] = {
129                 0, 255, 76, 150, 29,
130         };
131         unsigned char cb[width * height] = {
132                 128, 128, 85, 44, 255,
133         };
134         unsigned char cr[width * height] = {
135                 128, 128, 255, 21, 107,
136         };
137         float expected_data[4 * width * height] = {
138                 0.0, 0.0, 0.0, 1.0,
139                 1.0, 1.0, 1.0, 1.0,
140                 1.0, 0.0, 0.0, 1.0,
141                 0.0, 1.0, 0.0, 1.0,
142                 0.0, 0.0, 1.0, 1.0,
143         };
144         float out_data[4 * width * height];
145
146         EffectChainTester tester(NULL, width, height);
147
148         ImageFormat format;
149         format.color_space = COLORSPACE_sRGB;
150         format.gamma_curve = GAMMA_sRGB;
151
152         YCbCrFormat ycbcr_format;
153         ycbcr_format.luma_coefficients = YCBCR_REC_601;
154         ycbcr_format.full_range = true;
155         ycbcr_format.num_levels = 256;
156         ycbcr_format.chroma_subsampling_x = 1;
157         ycbcr_format.chroma_subsampling_y = 1;
158         ycbcr_format.cb_x_position = 0.5f;
159         ycbcr_format.cb_y_position = 0.5f;
160         ycbcr_format.cr_x_position = 0.5f;
161         ycbcr_format.cr_y_position = 0.5f;
162
163         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
164         input->set_pixel_data(0, y);
165         input->set_pixel_data(1, cb);
166         input->set_pixel_data(2, cr);
167         tester.get_chain()->add_input(input);
168
169         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
170
171         // Y'CbCr isn't 100% accurate (the input values are rounded),
172         // so we need some leeway.
173         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
174 }
175
176 TEST(YCbCrInputTest, Rec709) {
177         const int width = 1;
178         const int height = 5;
179
180         // Pure-color test inputs, calculated with the formulas in Rec. 709
181         // page 19, items 3.4 and 3.5.
182         unsigned char y[width * height] = {
183                 16, 235, 63, 173, 32, 
184         };
185         unsigned char cb[width * height] = {
186                 128, 128, 102, 42, 240,
187         };
188         unsigned char cr[width * height] = {
189                 128, 128, 240, 26, 118,
190         };
191         float expected_data[4 * width * height] = {
192                 0.0, 0.0, 0.0, 1.0,
193                 1.0, 1.0, 1.0, 1.0,
194                 1.0, 0.0, 0.0, 1.0,
195                 0.0, 1.0, 0.0, 1.0,
196                 0.0, 0.0, 1.0, 1.0,
197         };
198         float out_data[4 * width * height];
199
200         EffectChainTester tester(NULL, width, height);
201
202         ImageFormat format;
203         format.color_space = COLORSPACE_sRGB;
204         format.gamma_curve = GAMMA_sRGB;
205
206         YCbCrFormat ycbcr_format;
207         ycbcr_format.luma_coefficients = YCBCR_REC_709;
208         ycbcr_format.full_range = false;
209         ycbcr_format.num_levels = 256;
210         ycbcr_format.chroma_subsampling_x = 1;
211         ycbcr_format.chroma_subsampling_y = 1;
212         ycbcr_format.cb_x_position = 0.5f;
213         ycbcr_format.cb_y_position = 0.5f;
214         ycbcr_format.cr_x_position = 0.5f;
215         ycbcr_format.cr_y_position = 0.5f;
216
217         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
218         input->set_pixel_data(0, y);
219         input->set_pixel_data(1, cb);
220         input->set_pixel_data(2, cr);
221         tester.get_chain()->add_input(input);
222
223         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
224
225         // Y'CbCr isn't 100% accurate (the input values are rounded),
226         // so we need some leeway.
227         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
228 }
229
230 TEST(YCbCrInputTest, Rec2020) {
231         const int width = 1;
232         const int height = 5;
233
234         // Pure-color test inputs, calculated with the formulas in Rec. 2020
235         // page 4, tables 4 and 5 (for conventional non-constant luminance).
236         // Note that we still use 8-bit inputs, even though Rec. 2020 is only
237         // defined for 10- and 12-bit.
238         unsigned char y[width * height] = {
239                 16, 235, 74, 164, 29,
240         };
241         unsigned char cb[width * height] = {
242                 128, 128, 97, 47, 240,
243         };
244         unsigned char cr[width * height] = {
245                 128, 128, 240, 25, 119,
246         };
247         float expected_data[4 * width * height] = {
248                 0.0, 0.0, 0.0, 1.0,
249                 1.0, 1.0, 1.0, 1.0,
250                 1.0, 0.0, 0.0, 1.0,
251                 0.0, 1.0, 0.0, 1.0,
252                 0.0, 0.0, 1.0, 1.0,
253         };
254         float out_data[4 * width * height];
255
256         EffectChainTester tester(NULL, width, height);
257
258         ImageFormat format;
259         format.color_space = COLORSPACE_sRGB;
260         format.gamma_curve = GAMMA_sRGB;
261
262         YCbCrFormat ycbcr_format;
263         ycbcr_format.luma_coefficients = YCBCR_REC_2020;
264         ycbcr_format.full_range = false;
265         ycbcr_format.num_levels = 256;
266         ycbcr_format.chroma_subsampling_x = 1;
267         ycbcr_format.chroma_subsampling_y = 1;
268         ycbcr_format.cb_x_position = 0.5f;
269         ycbcr_format.cb_y_position = 0.5f;
270         ycbcr_format.cr_x_position = 0.5f;
271         ycbcr_format.cr_y_position = 0.5f;
272
273         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
274         input->set_pixel_data(0, y);
275         input->set_pixel_data(1, cb);
276         input->set_pixel_data(2, cr);
277         tester.get_chain()->add_input(input);
278
279         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
280
281         // Y'CbCr isn't 100% accurate (the input values are rounded),
282         // so we need some leeway.
283         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
284 }
285
286 TEST(YCbCrInputTest, Subsampling420) {
287         const int width = 4;
288         const int height = 4;
289
290         unsigned char y[width * height] = {
291                 126, 126, 126, 126,
292                 126, 126, 126, 126,
293                 126, 126, 126, 126,
294                 126, 126, 126, 126,
295         };
296         unsigned char cb[(width/2) * (height/2)] = {
297                 64, 128,
298                 128, 192,
299         };
300         unsigned char cr[(width/2) * (height/2)] = {
301                 128, 128,
302                 128, 128,
303         };
304
305         // Note: This is only the blue channel. The chroma samples (with associated
306         // values for blue) are marked off in comments.
307         float expected_data[width * height] = {
308                 0.000, 0.125, 0.375, 0.500, 
309                  /* 0.0 */      /* 0.5 */
310                 0.125, 0.250, 0.500, 0.625,
311
312                 0.375, 0.500, 0.750, 0.875,
313                  /* 0.5 */      /* 1.0 */
314                 0.500, 0.625, 0.875, 1.000,
315         };
316         float out_data[width * height];
317
318         EffectChainTester tester(NULL, width, height);
319
320         ImageFormat format;
321         format.color_space = COLORSPACE_sRGB;
322         format.gamma_curve = GAMMA_sRGB;
323
324         YCbCrFormat ycbcr_format;
325         ycbcr_format.luma_coefficients = YCBCR_REC_601;
326         ycbcr_format.full_range = false;
327         ycbcr_format.num_levels = 256;
328         ycbcr_format.chroma_subsampling_x = 2;
329         ycbcr_format.chroma_subsampling_y = 2;
330         ycbcr_format.cb_x_position = 0.5f;
331         ycbcr_format.cb_y_position = 0.5f;
332         ycbcr_format.cr_x_position = 0.5f;
333         ycbcr_format.cr_y_position = 0.5f;
334
335         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
336         input->set_pixel_data(0, y);
337         input->set_pixel_data(1, cb);
338         input->set_pixel_data(2, cr);
339         tester.get_chain()->add_input(input);
340
341         tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
342
343         // Y'CbCr isn't 100% accurate (the input values are rounded),
344         // so we need some leeway.
345         expect_equal(expected_data, out_data, width, height, 0.01, 0.001);
346 }
347
348 TEST(YCbCrInputTest, Subsampling420WithNonCenteredSamples) {
349         const int width = 4;
350         const int height = 4;
351
352         unsigned char y[width * height] = {
353                 126, 126, 126, 126,
354                 126, 126, 126, 126,
355                 126, 126, 126, 126,
356                 126, 126, 126, 126,
357         };
358         unsigned char cb[(width/2) * (height/2)] = {
359                 64, 128,
360                 128, 192,
361         };
362         unsigned char cr[(width/2) * (height/2)] = {
363                 128, 128,
364                 128, 128,
365         };
366
367         // Note: This is only the blue channel. The chroma samples (with associated
368         // values for blue) are marked off in comments.
369         float expected_data[width * height] = {
370                    0.000, 0.250, 0.500, 0.500, 
371                 /* 0.0 */     /* 0.5 */
372                    0.125, 0.375, 0.625, 0.625,
373
374                    0.375, 0.625, 0.875, 0.875,
375                 /* 0.5 */     /* 1.0 */
376                    0.500, 0.750, 1.000, 1.000,
377         };
378         float out_data[width * height];
379
380         EffectChainTester tester(NULL, width, height);
381
382         ImageFormat format;
383         format.color_space = COLORSPACE_sRGB;
384         format.gamma_curve = GAMMA_sRGB;
385
386         YCbCrFormat ycbcr_format;
387         ycbcr_format.luma_coefficients = YCBCR_REC_601;
388         ycbcr_format.full_range = false;
389         ycbcr_format.num_levels = 256;
390         ycbcr_format.chroma_subsampling_x = 2;
391         ycbcr_format.chroma_subsampling_y = 2;
392         ycbcr_format.cb_x_position = 0.0f;
393         ycbcr_format.cb_y_position = 0.5f;
394         ycbcr_format.cr_x_position = 0.0f;
395         ycbcr_format.cr_y_position = 0.5f;
396
397         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
398         input->set_pixel_data(0, y);
399         input->set_pixel_data(1, cb);
400         input->set_pixel_data(2, cr);
401         tester.get_chain()->add_input(input);
402
403         tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
404
405         // Y'CbCr isn't 100% accurate (the input values are rounded),
406         // so we need some leeway.
407         expect_equal(expected_data, out_data, width, height, 0.01, 0.0012);
408 }
409
410 // Yes, some 4:2:2 formats actually have this craziness.
411 TEST(YCbCrInputTest, DifferentCbAndCrPositioning) {
412         const int width = 4;
413         const int height = 4;
414
415         unsigned char y[width * height] = {
416                 126, 126, 126, 126,
417                 126, 126, 126, 126,
418                 126, 126, 126, 126,
419                 126, 126, 126, 126,
420         };
421         unsigned char cb[(width/2) * height] = {
422                 64, 128,
423                 128, 192,
424                 128, 128,
425                 128, 128,
426         };
427         unsigned char cr[(width/2) * height] = {
428                 48, 128,
429                 128, 208,
430                 128, 128,
431                 128, 128,
432         };
433
434         // Chroma samples in this csae are always co-sited with a luma sample;
435         // their associated color values and position are marked off in comments.
436         float expected_data_blue[width * height] = {
437                    0.000 /* 0.0 */, 0.250,           0.500 /* 0.5 */, 0.500, 
438                    0.500 /* 0.5 */, 0.750,           1.000 /* 1.0 */, 1.000, 
439                    0.500 /* 0.5 */, 0.500,           0.500 /* 0.5 */, 0.500, 
440                    0.500 /* 0.5 */, 0.500,           0.500 /* 0.5 */, 0.500, 
441         };
442         float expected_data_red[width * height] = {
443                    0.000,           0.000 /* 0.0 */, 0.250,           0.500 /* 0.5 */, 
444                    0.500,           0.500 /* 0.5 */, 0.750,           1.000 /* 1.0 */, 
445                    0.500,           0.500 /* 0.5 */, 0.500,           0.500 /* 0.5 */, 
446                    0.500,           0.500 /* 0.5 */, 0.500,           0.500 /* 0.5 */, 
447         };
448         float out_data[width * height];
449
450         EffectChainTester tester(NULL, width, height);
451
452         ImageFormat format;
453         format.color_space = COLORSPACE_sRGB;
454         format.gamma_curve = GAMMA_sRGB;
455
456         YCbCrFormat ycbcr_format;
457         ycbcr_format.luma_coefficients = YCBCR_REC_601;
458         ycbcr_format.full_range = false;
459         ycbcr_format.num_levels = 256;
460         ycbcr_format.chroma_subsampling_x = 2;
461         ycbcr_format.chroma_subsampling_y = 1;
462         ycbcr_format.cb_x_position = 0.0f;
463         ycbcr_format.cb_y_position = 0.5f;
464         ycbcr_format.cr_x_position = 1.0f;
465         ycbcr_format.cr_y_position = 0.5f;
466
467         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
468         input->set_pixel_data(0, y);
469         input->set_pixel_data(1, cb);
470         input->set_pixel_data(2, cr);
471         tester.get_chain()->add_input(input);
472
473         // Y'CbCr isn't 100% accurate (the input values are rounded),
474         // so we need some leeway.
475         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
476         expect_equal(expected_data_red, out_data, width, height, 0.02, 0.002);
477
478         tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
479         expect_equal(expected_data_blue, out_data, width, height, 0.01, 0.001);
480 }
481
482 TEST(YCbCrInputTest, PBO) {
483         const int width = 1;
484         const int height = 5;
485
486         // Pure-color test inputs, calculated with the formulas in Rec. 601
487         // section 2.5.4.
488         unsigned char data[width * height * 3] = {
489                 16, 235, 81, 145, 41,
490                 128, 128, 90, 54, 240,
491                 128, 128, 240, 34, 110,
492         };
493         float expected_data[4 * width * height] = {
494                 0.0, 0.0, 0.0, 1.0,
495                 1.0, 1.0, 1.0, 1.0,
496                 1.0, 0.0, 0.0, 1.0,
497                 0.0, 1.0, 0.0, 1.0,
498                 0.0, 0.0, 1.0, 1.0,
499         };
500         float out_data[4 * width * height];
501
502         GLuint pbo;
503         glGenBuffers(1, &pbo);
504         glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo);
505         glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, width * height * 3, data, GL_STREAM_DRAW);
506         glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
507
508         EffectChainTester tester(NULL, width, height);
509
510         ImageFormat format;
511         format.color_space = COLORSPACE_sRGB;
512         format.gamma_curve = GAMMA_sRGB;
513
514         YCbCrFormat ycbcr_format;
515         ycbcr_format.luma_coefficients = YCBCR_REC_601;
516         ycbcr_format.full_range = false;
517         ycbcr_format.num_levels = 256;
518         ycbcr_format.chroma_subsampling_x = 1;
519         ycbcr_format.chroma_subsampling_y = 1;
520         ycbcr_format.cb_x_position = 0.5f;
521         ycbcr_format.cb_y_position = 0.5f;
522         ycbcr_format.cr_x_position = 0.5f;
523         ycbcr_format.cr_y_position = 0.5f;
524
525         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
526         input->set_pixel_data(0, (unsigned char *)BUFFER_OFFSET(0), pbo);
527         input->set_pixel_data(1, (unsigned char *)BUFFER_OFFSET(width * height), pbo);
528         input->set_pixel_data(2, (unsigned char *)BUFFER_OFFSET(width * height * 2), pbo);
529         tester.get_chain()->add_input(input);
530
531         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
532
533         // Y'CbCr isn't 100% accurate (the input values are rounded),
534         // so we need some leeway.
535         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
536
537         glDeleteBuffers(1, &pbo);
538 }
539
540 TEST(YCbCrInputTest, CombinedCbAndCr) {
541         const int width = 1;
542         const int height = 5;
543
544         // Pure-color test inputs, calculated with the formulas in Rec. 601
545         // section 2.5.4.
546         unsigned char y[width * height] = {
547                 16, 235, 81, 145, 41,
548         };
549         unsigned char cb_cr[width * height * 2] = {
550                 128, 128,
551                 128, 128,
552                  90, 240,
553                  54,  34,
554                 240, 110,
555         };
556         float expected_data[4 * width * height] = {
557                 0.0, 0.0, 0.0, 1.0,
558                 1.0, 1.0, 1.0, 1.0,
559                 1.0, 0.0, 0.0, 1.0,
560                 0.0, 1.0, 0.0, 1.0,
561                 0.0, 0.0, 1.0, 1.0,
562         };
563         float out_data[4 * width * height];
564
565         EffectChainTester tester(NULL, width, height);
566
567         ImageFormat format;
568         format.color_space = COLORSPACE_sRGB;
569         format.gamma_curve = GAMMA_sRGB;
570
571         YCbCrFormat ycbcr_format;
572         ycbcr_format.luma_coefficients = YCBCR_REC_601;
573         ycbcr_format.full_range = false;
574         ycbcr_format.num_levels = 256;
575         ycbcr_format.chroma_subsampling_x = 1;
576         ycbcr_format.chroma_subsampling_y = 1;
577         ycbcr_format.cb_x_position = 0.5f;
578         ycbcr_format.cb_y_position = 0.5f;
579         ycbcr_format.cr_x_position = 0.5f;
580         ycbcr_format.cr_y_position = 0.5f;
581
582         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_SPLIT_Y_AND_CBCR);
583         input->set_pixel_data(0, y);
584         input->set_pixel_data(1, cb_cr);
585         tester.get_chain()->add_input(input);
586
587         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
588
589         // Y'CbCr isn't 100% accurate (the input values are rounded),
590         // so we need some leeway.
591         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
592 }
593
594 TEST(YCbCrInputTest, ExternalTexture) {
595         const int width = 1;
596         const int height = 5;
597
598         // Pure-color test inputs, calculated with the formulas in Rec. 601
599         // section 2.5.4.
600         unsigned char y[width * height] = {
601                 16, 235, 81, 145, 41,
602         };
603         unsigned char cb[width * height] = {
604                 128, 128, 90, 54, 240,
605         };
606         unsigned char cr[width * height] = {
607                 128, 128, 240, 34, 110,
608         };
609         float expected_data[4 * width * height] = {
610                 0.0, 0.0, 0.0, 1.0,
611                 1.0, 1.0, 1.0, 1.0,
612                 1.0, 0.0, 0.0, 1.0,
613                 0.0, 1.0, 0.0, 1.0,
614                 0.0, 0.0, 1.0, 1.0,
615         };
616         float out_data[4 * width * height];
617
618         EffectChainTester tester(NULL, width, height);
619
620         ImageFormat format;
621         format.color_space = COLORSPACE_sRGB;
622         format.gamma_curve = GAMMA_sRGB;
623
624         YCbCrFormat ycbcr_format;
625         ycbcr_format.luma_coefficients = YCBCR_REC_601;
626         ycbcr_format.full_range = false;
627         ycbcr_format.num_levels = 256;
628         ycbcr_format.chroma_subsampling_x = 1;
629         ycbcr_format.chroma_subsampling_y = 1;
630         ycbcr_format.cb_x_position = 0.5f;
631         ycbcr_format.cb_y_position = 0.5f;
632         ycbcr_format.cr_x_position = 0.5f;
633         ycbcr_format.cr_y_position = 0.5f;
634
635         // Make a texture for the Cb data; keep the others as regular uploads.
636         ResourcePool pool;
637         GLuint cb_tex = pool.create_2d_texture(GL_R8, width, height);
638         check_error();
639         glBindTexture(GL_TEXTURE_2D, cb_tex);
640         check_error();
641         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
642         check_error();
643         glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
644         check_error();
645         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, cb);
646         check_error();
647         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
648         check_error();
649         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
650         check_error();
651
652         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
653         input->set_pixel_data(0, y);
654         input->set_texture_num(1, cb_tex);
655         input->set_pixel_data(2, cr);
656         tester.get_chain()->add_input(input);
657
658         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
659
660         pool.release_2d_texture(cb_tex);
661
662         // Y'CbCr isn't 100% accurate (the input values are rounded),
663         // so we need some leeway.
664         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
665 }
666
667 TEST(YCbCrTest, WikipediaRec601ForwardMatrix) {
668         YCbCrFormat ycbcr_format;
669         ycbcr_format.luma_coefficients = YCBCR_REC_601;
670         ycbcr_format.full_range = false;
671         ycbcr_format.num_levels = 256;
672
673         float offset[3];
674         Eigen::Matrix3d ycbcr_to_rgb;
675         compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
676
677         Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse() * 255.0;
678
679         // Values from https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion.
680         EXPECT_NEAR(  65.481, rgb_to_ycbcr(0,0), 1e-3);
681         EXPECT_NEAR( 128.553, rgb_to_ycbcr(0,1), 1e-3);
682         EXPECT_NEAR(  24.966, rgb_to_ycbcr(0,2), 1e-3);
683
684         EXPECT_NEAR( -37.797, rgb_to_ycbcr(1,0), 1e-3);
685         EXPECT_NEAR( -74.203, rgb_to_ycbcr(1,1), 1e-3);
686         EXPECT_NEAR( 112.000, rgb_to_ycbcr(1,2), 1e-3);
687
688         EXPECT_NEAR( 112.000, rgb_to_ycbcr(2,0), 1e-3);
689         EXPECT_NEAR( -93.786, rgb_to_ycbcr(2,1), 1e-3);
690         EXPECT_NEAR( -18.214, rgb_to_ycbcr(2,2), 1e-3);
691
692         EXPECT_NEAR( 16.0, offset[0] * 255.0, 1e-3);
693         EXPECT_NEAR(128.0, offset[1] * 255.0, 1e-3);
694         EXPECT_NEAR(128.0, offset[2] * 255.0, 1e-3);
695 }
696
697 TEST(YCbCrTest, WikipediaJPEGMatrices) {
698         YCbCrFormat ycbcr_format;
699         ycbcr_format.luma_coefficients = YCBCR_REC_601;
700         ycbcr_format.full_range = true;
701         ycbcr_format.num_levels = 256;
702
703         float offset[3];
704         Eigen::Matrix3d ycbcr_to_rgb;
705         compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
706
707         // Values from https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion.
708         EXPECT_NEAR( 1.00000, ycbcr_to_rgb(0,0), 1e-5);
709         EXPECT_NEAR( 0.00000, ycbcr_to_rgb(0,1), 1e-5);
710         EXPECT_NEAR( 1.40200, ycbcr_to_rgb(0,2), 1e-5);
711
712         EXPECT_NEAR( 1.00000, ycbcr_to_rgb(1,0), 1e-5);
713         EXPECT_NEAR(-0.34414, ycbcr_to_rgb(1,1), 1e-5);
714         EXPECT_NEAR(-0.71414, ycbcr_to_rgb(1,2), 1e-5);
715
716         EXPECT_NEAR( 1.00000, ycbcr_to_rgb(2,0), 1e-5);
717         EXPECT_NEAR( 1.77200, ycbcr_to_rgb(2,1), 1e-5);
718         EXPECT_NEAR( 0.00000, ycbcr_to_rgb(2,2), 1e-5);
719
720         Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse();
721
722         // Same.
723         EXPECT_NEAR( 0.299000, rgb_to_ycbcr(0,0), 1e-6);
724         EXPECT_NEAR( 0.587000, rgb_to_ycbcr(0,1), 1e-6);
725         EXPECT_NEAR( 0.114000, rgb_to_ycbcr(0,2), 1e-6);
726
727         EXPECT_NEAR(-0.168736, rgb_to_ycbcr(1,0), 1e-6);
728         EXPECT_NEAR(-0.331264, rgb_to_ycbcr(1,1), 1e-6);
729         EXPECT_NEAR( 0.500000, rgb_to_ycbcr(1,2), 1e-6);
730
731         EXPECT_NEAR( 0.500000, rgb_to_ycbcr(2,0), 1e-6);
732         EXPECT_NEAR(-0.418688, rgb_to_ycbcr(2,1), 1e-6);
733         EXPECT_NEAR(-0.081312, rgb_to_ycbcr(2,2), 1e-6);
734
735         EXPECT_NEAR(  0.0, offset[0] * 255.0, 1e-3);
736         EXPECT_NEAR(128.0, offset[1] * 255.0, 1e-3);
737         EXPECT_NEAR(128.0, offset[2] * 255.0, 1e-3);
738 }
739
740 TEST(YCbCrTest, BlackmagicForwardMatrix) {
741         YCbCrFormat ycbcr_format;
742         ycbcr_format.luma_coefficients = YCBCR_REC_709;
743         ycbcr_format.full_range = false;
744         ycbcr_format.num_levels = 256;
745
746         float offset[3];
747         Eigen::Matrix3d ycbcr_to_rgb;
748         compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
749
750         Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse();
751
752         // Values from DeckLink SDK documentation.
753         EXPECT_NEAR( 0.183, rgb_to_ycbcr(0,0), 1e-3);
754         EXPECT_NEAR( 0.614, rgb_to_ycbcr(0,1), 1e-3);
755         EXPECT_NEAR( 0.062, rgb_to_ycbcr(0,2), 1e-3);
756
757         EXPECT_NEAR(-0.101, rgb_to_ycbcr(1,0), 1e-3);
758         EXPECT_NEAR(-0.338, rgb_to_ycbcr(1,1), 1e-3);
759         EXPECT_NEAR( 0.439, rgb_to_ycbcr(1,2), 1e-3);
760
761         EXPECT_NEAR( 0.439, rgb_to_ycbcr(2,0), 1e-3);
762         EXPECT_NEAR(-0.399, rgb_to_ycbcr(2,1), 1e-3);
763         EXPECT_NEAR(-0.040, rgb_to_ycbcr(2,2), 1e-3);
764
765         EXPECT_NEAR( 16.0, offset[0] * 255.0, 1e-3);
766         EXPECT_NEAR(128.0, offset[1] * 255.0, 1e-3);
767         EXPECT_NEAR(128.0, offset[2] * 255.0, 1e-3);
768 }
769
770 TEST(YCbCrInputTest, NoData) {
771         const int width = 1;
772         const int height = 5;
773
774         float out_data[4 * width * height];
775
776         EffectChainTester tester(NULL, width, height);
777
778         ImageFormat format;
779         format.color_space = COLORSPACE_sRGB;
780         format.gamma_curve = GAMMA_sRGB;
781
782         YCbCrFormat ycbcr_format;
783         ycbcr_format.luma_coefficients = YCBCR_REC_601;
784         ycbcr_format.full_range = false;
785         ycbcr_format.num_levels = 256;
786         ycbcr_format.chroma_subsampling_x = 1;
787         ycbcr_format.chroma_subsampling_y = 1;
788         ycbcr_format.cb_x_position = 0.5f;
789         ycbcr_format.cb_y_position = 0.5f;
790         ycbcr_format.cr_x_position = 0.5f;
791         ycbcr_format.cr_y_position = 0.5f;
792
793         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
794         tester.get_chain()->add_input(input);
795
796         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
797
798         // Don't care what the output was, just that it does not crash.
799 }
800
801 TEST(YCbCrInputTest, TenBitInterleaved) {
802         const int width = 1;
803         const int height = 5;
804
805         // Pure-color inputs, calculated using formulas 3.2, 3.3 and 3.4 from
806         // Rec. 709. (Except the first two, which are obvious given the 64–940
807         // range of luminance.)
808         unsigned expanded_data[width * height * 3] = {
809                  64, 512, 512,
810                 940, 512, 512,
811                 250, 409, 960,
812                 691, 167, 105,
813                 127, 960, 471,
814         };
815         float expected_data[4 * width * height] = {
816                 0.0, 0.0, 0.0, 1.0,
817                 1.0, 1.0, 1.0, 1.0,
818                 1.0, 0.0, 0.0, 1.0,
819                 0.0, 1.0, 0.0, 1.0,
820                 0.0, 0.0, 1.0, 1.0,
821         };
822         float out_data[4 * width * height];
823
824         // Pack 32:32:32 to 10:10:10:2.
825         uint32_t data[width * height];
826         for (unsigned i = 0; i < width * height; ++i) {
827                 data[i] =
828                          expanded_data[i * 3 + 0]        |
829                         (expanded_data[i * 3 + 1] << 10) |
830                         (expanded_data[i * 3 + 2] << 20);
831         }
832
833         EffectChainTester tester(NULL, width, height);
834
835         ImageFormat format;
836         format.color_space = COLORSPACE_sRGB;
837         format.gamma_curve = GAMMA_sRGB;
838
839         YCbCrFormat ycbcr_format;
840         ycbcr_format.luma_coefficients = YCBCR_REC_709;
841         ycbcr_format.full_range = false;
842         ycbcr_format.num_levels = 1024;  // 10-bit.
843         ycbcr_format.chroma_subsampling_x = 1;
844         ycbcr_format.chroma_subsampling_y = 1;
845         ycbcr_format.cb_x_position = 0.5f;
846         ycbcr_format.cb_y_position = 0.5f;
847         ycbcr_format.cr_x_position = 0.5f;
848         ycbcr_format.cr_y_position = 0.5f;
849
850         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_INTERLEAVED, GL_UNSIGNED_INT_2_10_10_10_REV);
851         input->set_pixel_data(0, data);
852         tester.get_chain()->add_input(input);
853
854         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
855
856         // We can set much tighter limits on this than 8-bit Y'CbCr;
857         // even tighter than the default limits.
858         expect_equal(expected_data, out_data, 4 * width, height, 0.002, 0.0003);
859 }
860
861 }  // namespace movit