]> git.sesse.net Git - movit/blob - ycbcr_input_test.cpp
Fix a bad typo.
[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 using namespace std;
17
18 namespace movit {
19
20 TEST(YCbCrInputTest, Simple444) {
21         const int width = 1;
22         const int height = 5;
23
24         // Pure-color test inputs, calculated with the formulas in Rec. 601
25         // section 2.5.4.
26         unsigned char y[width * height] = {
27                 16, 235, 81, 145, 41,
28         };
29         unsigned char cb[width * height] = {
30                 128, 128, 90, 54, 240,
31         };
32         unsigned char cr[width * height] = {
33                 128, 128, 240, 34, 110,
34         };
35         float expected_data[4 * width * height] = {
36                 0.0, 0.0, 0.0, 1.0,
37                 1.0, 1.0, 1.0, 1.0,
38                 1.0, 0.0, 0.0, 1.0,
39                 0.0, 1.0, 0.0, 1.0,
40                 0.0, 0.0, 1.0, 1.0,
41         };
42         float out_data[4 * width * height];
43
44         EffectChainTester tester(NULL, width, height);
45
46         ImageFormat format;
47         format.color_space = COLORSPACE_sRGB;
48         format.gamma_curve = GAMMA_sRGB;
49
50         YCbCrFormat ycbcr_format;
51         ycbcr_format.luma_coefficients = YCBCR_REC_601;
52         ycbcr_format.full_range = false;
53         ycbcr_format.num_levels = 256;
54         ycbcr_format.chroma_subsampling_x = 1;
55         ycbcr_format.chroma_subsampling_y = 1;
56         ycbcr_format.cb_x_position = 0.5f;
57         ycbcr_format.cb_y_position = 0.5f;
58         ycbcr_format.cr_x_position = 0.5f;
59         ycbcr_format.cr_y_position = 0.5f;
60
61         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
62         input->set_pixel_data(0, y);
63         input->set_pixel_data(1, cb);
64         input->set_pixel_data(2, cr);
65         tester.get_chain()->add_input(input);
66
67         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
68
69         // Y'CbCr isn't 100% accurate (the input values are rounded),
70         // so we need some leeway.
71         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
72 }
73
74 TEST(YCbCrInputTest, Interleaved444) {
75         const int width = 1;
76         const int height = 5;
77
78         // Same data as Simple444, just rearranged.
79         unsigned char data[width * height * 3] = {
80                  16, 128, 128,
81                 235, 128, 128,
82                  81,  90, 240,
83                 145,  54,  34,
84                  41, 240, 110,
85         };
86         float expected_data[4 * width * height] = {
87                 0.0, 0.0, 0.0, 1.0,
88                 1.0, 1.0, 1.0, 1.0,
89                 1.0, 0.0, 0.0, 1.0,
90                 0.0, 1.0, 0.0, 1.0,
91                 0.0, 0.0, 1.0, 1.0,
92         };
93         float out_data[4 * width * height];
94
95         EffectChainTester tester(NULL, width, height);
96
97         ImageFormat format;
98         format.color_space = COLORSPACE_sRGB;
99         format.gamma_curve = GAMMA_sRGB;
100
101         YCbCrFormat ycbcr_format;
102         ycbcr_format.luma_coefficients = YCBCR_REC_601;
103         ycbcr_format.full_range = false;
104         ycbcr_format.num_levels = 256;
105         ycbcr_format.chroma_subsampling_x = 1;
106         ycbcr_format.chroma_subsampling_y = 1;
107         ycbcr_format.cb_x_position = 0.5f;
108         ycbcr_format.cb_y_position = 0.5f;
109         ycbcr_format.cr_x_position = 0.5f;
110         ycbcr_format.cr_y_position = 0.5f;
111
112         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_INTERLEAVED);
113         input->set_pixel_data(0, data);
114         tester.get_chain()->add_input(input);
115
116         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
117
118         // Y'CbCr isn't 100% accurate (the input values are rounded),
119         // so we need some leeway.
120         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
121 }
122
123 TEST(YCbCrInputTest, FullRangeRec601) {
124         const int width = 1;
125         const int height = 5;
126
127         // Pure-color test inputs, calculated with the formulas in Rec. 601
128         // section 2.5.4 but without the scaling factors applied
129         // (so both R, G, B, Y, Cb and R vary from 0 to 255).
130         unsigned char y[width * height] = {
131                 0, 255, 76, 150, 29,
132         };
133         unsigned char cb[width * height] = {
134                 128, 128, 85, 44, 255,
135         };
136         unsigned char cr[width * height] = {
137                 128, 128, 255, 21, 107,
138         };
139         float expected_data[4 * width * height] = {
140                 0.0, 0.0, 0.0, 1.0,
141                 1.0, 1.0, 1.0, 1.0,
142                 1.0, 0.0, 0.0, 1.0,
143                 0.0, 1.0, 0.0, 1.0,
144                 0.0, 0.0, 1.0, 1.0,
145         };
146         float out_data[4 * width * height];
147
148         EffectChainTester tester(NULL, width, height);
149
150         ImageFormat format;
151         format.color_space = COLORSPACE_sRGB;
152         format.gamma_curve = GAMMA_sRGB;
153
154         YCbCrFormat ycbcr_format;
155         ycbcr_format.luma_coefficients = YCBCR_REC_601;
156         ycbcr_format.full_range = true;
157         ycbcr_format.num_levels = 256;
158         ycbcr_format.chroma_subsampling_x = 1;
159         ycbcr_format.chroma_subsampling_y = 1;
160         ycbcr_format.cb_x_position = 0.5f;
161         ycbcr_format.cb_y_position = 0.5f;
162         ycbcr_format.cr_x_position = 0.5f;
163         ycbcr_format.cr_y_position = 0.5f;
164
165         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
166         input->set_pixel_data(0, y);
167         input->set_pixel_data(1, cb);
168         input->set_pixel_data(2, cr);
169         tester.get_chain()->add_input(input);
170
171         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
172
173         // Y'CbCr isn't 100% accurate (the input values are rounded),
174         // so we need some leeway.
175         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
176 }
177
178 TEST(YCbCrInputTest, Rec709) {
179         const int width = 1;
180         const int height = 5;
181
182         // Pure-color test inputs, calculated with the formulas in Rec. 709
183         // page 19, items 3.4 and 3.5.
184         unsigned char y[width * height] = {
185                 16, 235, 63, 173, 32, 
186         };
187         unsigned char cb[width * height] = {
188                 128, 128, 102, 42, 240,
189         };
190         unsigned char cr[width * height] = {
191                 128, 128, 240, 26, 118,
192         };
193         float expected_data[4 * width * height] = {
194                 0.0, 0.0, 0.0, 1.0,
195                 1.0, 1.0, 1.0, 1.0,
196                 1.0, 0.0, 0.0, 1.0,
197                 0.0, 1.0, 0.0, 1.0,
198                 0.0, 0.0, 1.0, 1.0,
199         };
200         float out_data[4 * width * height];
201
202         EffectChainTester tester(NULL, width, height);
203
204         ImageFormat format;
205         format.color_space = COLORSPACE_sRGB;
206         format.gamma_curve = GAMMA_sRGB;
207
208         YCbCrFormat ycbcr_format;
209         ycbcr_format.luma_coefficients = YCBCR_REC_709;
210         ycbcr_format.full_range = false;
211         ycbcr_format.num_levels = 256;
212         ycbcr_format.chroma_subsampling_x = 1;
213         ycbcr_format.chroma_subsampling_y = 1;
214         ycbcr_format.cb_x_position = 0.5f;
215         ycbcr_format.cb_y_position = 0.5f;
216         ycbcr_format.cr_x_position = 0.5f;
217         ycbcr_format.cr_y_position = 0.5f;
218
219         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
220         input->set_pixel_data(0, y);
221         input->set_pixel_data(1, cb);
222         input->set_pixel_data(2, cr);
223         tester.get_chain()->add_input(input);
224
225         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
226
227         // Y'CbCr isn't 100% accurate (the input values are rounded),
228         // so we need some leeway.
229         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
230 }
231
232 TEST(YCbCrInputTest, Rec2020) {
233         const int width = 1;
234         const int height = 5;
235
236         // Pure-color test inputs, calculated with the formulas in Rec. 2020
237         // page 4, tables 4 and 5 (for conventional non-constant luminance).
238         // Note that we still use 8-bit inputs, even though Rec. 2020 is only
239         // defined for 10- and 12-bit.
240         unsigned char y[width * height] = {
241                 16, 235, 74, 164, 29,
242         };
243         unsigned char cb[width * height] = {
244                 128, 128, 97, 47, 240,
245         };
246         unsigned char cr[width * height] = {
247                 128, 128, 240, 25, 119,
248         };
249         float expected_data[4 * width * height] = {
250                 0.0, 0.0, 0.0, 1.0,
251                 1.0, 1.0, 1.0, 1.0,
252                 1.0, 0.0, 0.0, 1.0,
253                 0.0, 1.0, 0.0, 1.0,
254                 0.0, 0.0, 1.0, 1.0,
255         };
256         float out_data[4 * width * height];
257
258         EffectChainTester tester(NULL, width, height);
259
260         ImageFormat format;
261         format.color_space = COLORSPACE_sRGB;
262         format.gamma_curve = GAMMA_sRGB;
263
264         YCbCrFormat ycbcr_format;
265         ycbcr_format.luma_coefficients = YCBCR_REC_2020;
266         ycbcr_format.full_range = false;
267         ycbcr_format.num_levels = 256;
268         ycbcr_format.chroma_subsampling_x = 1;
269         ycbcr_format.chroma_subsampling_y = 1;
270         ycbcr_format.cb_x_position = 0.5f;
271         ycbcr_format.cb_y_position = 0.5f;
272         ycbcr_format.cr_x_position = 0.5f;
273         ycbcr_format.cr_y_position = 0.5f;
274
275         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
276         input->set_pixel_data(0, y);
277         input->set_pixel_data(1, cb);
278         input->set_pixel_data(2, cr);
279         tester.get_chain()->add_input(input);
280
281         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
282
283         // Y'CbCr isn't 100% accurate (the input values are rounded),
284         // so we need some leeway.
285         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
286 }
287
288 TEST(YCbCrInputTest, Subsampling420) {
289         const int width = 4;
290         const int height = 4;
291
292         unsigned char y[width * height] = {
293                 126, 126, 126, 126,
294                 126, 126, 126, 126,
295                 126, 126, 126, 126,
296                 126, 126, 126, 126,
297         };
298         unsigned char cb[(width/2) * (height/2)] = {
299                 64, 128,
300                 128, 192,
301         };
302         unsigned char cr[(width/2) * (height/2)] = {
303                 128, 128,
304                 128, 128,
305         };
306
307         // Note: This is only the blue channel. The chroma samples (with associated
308         // values for blue) are marked off in comments.
309         float expected_data[width * height] = {
310                 0.000, 0.125, 0.375, 0.500, 
311                  /* 0.0 */      /* 0.5 */
312                 0.125, 0.250, 0.500, 0.625,
313
314                 0.375, 0.500, 0.750, 0.875,
315                  /* 0.5 */      /* 1.0 */
316                 0.500, 0.625, 0.875, 1.000,
317         };
318         float out_data[width * height];
319
320         EffectChainTester tester(NULL, width, height);
321
322         ImageFormat format;
323         format.color_space = COLORSPACE_sRGB;
324         format.gamma_curve = GAMMA_sRGB;
325
326         YCbCrFormat ycbcr_format;
327         ycbcr_format.luma_coefficients = YCBCR_REC_601;
328         ycbcr_format.full_range = false;
329         ycbcr_format.num_levels = 256;
330         ycbcr_format.chroma_subsampling_x = 2;
331         ycbcr_format.chroma_subsampling_y = 2;
332         ycbcr_format.cb_x_position = 0.5f;
333         ycbcr_format.cb_y_position = 0.5f;
334         ycbcr_format.cr_x_position = 0.5f;
335         ycbcr_format.cr_y_position = 0.5f;
336
337         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
338         input->set_pixel_data(0, y);
339         input->set_pixel_data(1, cb);
340         input->set_pixel_data(2, cr);
341         tester.get_chain()->add_input(input);
342
343         tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
344
345         // Y'CbCr isn't 100% accurate (the input values are rounded),
346         // so we need some leeway.
347         expect_equal(expected_data, out_data, width, height, 0.01, 0.001);
348 }
349
350 TEST(YCbCrInputTest, Subsampling420WithNonCenteredSamples) {
351         const int width = 4;
352         const int height = 4;
353
354         unsigned char y[width * height] = {
355                 126, 126, 126, 126,
356                 126, 126, 126, 126,
357                 126, 126, 126, 126,
358                 126, 126, 126, 126,
359         };
360         unsigned char cb[(width/2) * (height/2)] = {
361                 64, 128,
362                 128, 192,
363         };
364         unsigned char cr[(width/2) * (height/2)] = {
365                 128, 128,
366                 128, 128,
367         };
368
369         // Note: This is only the blue channel. The chroma samples (with associated
370         // values for blue) are marked off in comments.
371         float expected_data[width * height] = {
372                    0.000, 0.250, 0.500, 0.500, 
373                 /* 0.0 */     /* 0.5 */
374                    0.125, 0.375, 0.625, 0.625,
375
376                    0.375, 0.625, 0.875, 0.875,
377                 /* 0.5 */     /* 1.0 */
378                    0.500, 0.750, 1.000, 1.000,
379         };
380         float out_data[width * height];
381
382         EffectChainTester tester(NULL, width, height);
383
384         ImageFormat format;
385         format.color_space = COLORSPACE_sRGB;
386         format.gamma_curve = GAMMA_sRGB;
387
388         YCbCrFormat ycbcr_format;
389         ycbcr_format.luma_coefficients = YCBCR_REC_601;
390         ycbcr_format.full_range = false;
391         ycbcr_format.num_levels = 256;
392         ycbcr_format.chroma_subsampling_x = 2;
393         ycbcr_format.chroma_subsampling_y = 2;
394         ycbcr_format.cb_x_position = 0.0f;
395         ycbcr_format.cb_y_position = 0.5f;
396         ycbcr_format.cr_x_position = 0.0f;
397         ycbcr_format.cr_y_position = 0.5f;
398
399         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
400         input->set_pixel_data(0, y);
401         input->set_pixel_data(1, cb);
402         input->set_pixel_data(2, cr);
403         tester.get_chain()->add_input(input);
404
405         tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
406
407         // Y'CbCr isn't 100% accurate (the input values are rounded),
408         // so we need some leeway.
409         expect_equal(expected_data, out_data, width, height, 0.01, 0.0012);
410 }
411
412 // Yes, some 4:2:2 formats actually have this craziness.
413 TEST(YCbCrInputTest, DifferentCbAndCrPositioning) {
414         const int width = 4;
415         const int height = 4;
416
417         unsigned char y[width * height] = {
418                 126, 126, 126, 126,
419                 126, 126, 126, 126,
420                 126, 126, 126, 126,
421                 126, 126, 126, 126,
422         };
423         unsigned char cb[(width/2) * height] = {
424                 64, 128,
425                 128, 192,
426                 128, 128,
427                 128, 128,
428         };
429         unsigned char cr[(width/2) * height] = {
430                 48, 128,
431                 128, 208,
432                 128, 128,
433                 128, 128,
434         };
435
436         // Chroma samples in this csae are always co-sited with a luma sample;
437         // their associated color values and position are marked off in comments.
438         float expected_data_blue[width * height] = {
439                    0.000 /* 0.0 */, 0.250,           0.500 /* 0.5 */, 0.500, 
440                    0.500 /* 0.5 */, 0.750,           1.000 /* 1.0 */, 1.000, 
441                    0.500 /* 0.5 */, 0.500,           0.500 /* 0.5 */, 0.500, 
442                    0.500 /* 0.5 */, 0.500,           0.500 /* 0.5 */, 0.500, 
443         };
444         float expected_data_red[width * height] = {
445                    0.000,           0.000 /* 0.0 */, 0.250,           0.500 /* 0.5 */, 
446                    0.500,           0.500 /* 0.5 */, 0.750,           1.000 /* 1.0 */, 
447                    0.500,           0.500 /* 0.5 */, 0.500,           0.500 /* 0.5 */, 
448                    0.500,           0.500 /* 0.5 */, 0.500,           0.500 /* 0.5 */, 
449         };
450         float out_data[width * height];
451
452         EffectChainTester tester(NULL, width, height);
453
454         ImageFormat format;
455         format.color_space = COLORSPACE_sRGB;
456         format.gamma_curve = GAMMA_sRGB;
457
458         YCbCrFormat ycbcr_format;
459         ycbcr_format.luma_coefficients = YCBCR_REC_601;
460         ycbcr_format.full_range = false;
461         ycbcr_format.num_levels = 256;
462         ycbcr_format.chroma_subsampling_x = 2;
463         ycbcr_format.chroma_subsampling_y = 1;
464         ycbcr_format.cb_x_position = 0.0f;
465         ycbcr_format.cb_y_position = 0.5f;
466         ycbcr_format.cr_x_position = 1.0f;
467         ycbcr_format.cr_y_position = 0.5f;
468
469         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
470         input->set_pixel_data(0, y);
471         input->set_pixel_data(1, cb);
472         input->set_pixel_data(2, cr);
473         tester.get_chain()->add_input(input);
474
475         // Y'CbCr isn't 100% accurate (the input values are rounded),
476         // so we need some leeway.
477         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
478         expect_equal(expected_data_red, out_data, width, height, 0.02, 0.002);
479
480         tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
481         expect_equal(expected_data_blue, out_data, width, height, 0.01, 0.001);
482 }
483
484 TEST(YCbCrInputTest, PBO) {
485         const int width = 1;
486         const int height = 5;
487
488         // Pure-color test inputs, calculated with the formulas in Rec. 601
489         // section 2.5.4.
490         unsigned char data[width * height * 3] = {
491                 16, 235, 81, 145, 41,
492                 128, 128, 90, 54, 240,
493                 128, 128, 240, 34, 110,
494         };
495         float expected_data[4 * width * height] = {
496                 0.0, 0.0, 0.0, 1.0,
497                 1.0, 1.0, 1.0, 1.0,
498                 1.0, 0.0, 0.0, 1.0,
499                 0.0, 1.0, 0.0, 1.0,
500                 0.0, 0.0, 1.0, 1.0,
501         };
502         float out_data[4 * width * height];
503
504         GLuint pbo;
505         glGenBuffers(1, &pbo);
506         glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo);
507         glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, width * height * 3, data, GL_STREAM_DRAW);
508         glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
509
510         EffectChainTester tester(NULL, width, height);
511
512         ImageFormat format;
513         format.color_space = COLORSPACE_sRGB;
514         format.gamma_curve = GAMMA_sRGB;
515
516         YCbCrFormat ycbcr_format;
517         ycbcr_format.luma_coefficients = YCBCR_REC_601;
518         ycbcr_format.full_range = false;
519         ycbcr_format.num_levels = 256;
520         ycbcr_format.chroma_subsampling_x = 1;
521         ycbcr_format.chroma_subsampling_y = 1;
522         ycbcr_format.cb_x_position = 0.5f;
523         ycbcr_format.cb_y_position = 0.5f;
524         ycbcr_format.cr_x_position = 0.5f;
525         ycbcr_format.cr_y_position = 0.5f;
526
527         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
528         input->set_pixel_data(0, (unsigned char *)BUFFER_OFFSET(0), pbo);
529         input->set_pixel_data(1, (unsigned char *)BUFFER_OFFSET(width * height), pbo);
530         input->set_pixel_data(2, (unsigned char *)BUFFER_OFFSET(width * height * 2), pbo);
531         tester.get_chain()->add_input(input);
532
533         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
534
535         // Y'CbCr isn't 100% accurate (the input values are rounded),
536         // so we need some leeway.
537         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
538
539         glDeleteBuffers(1, &pbo);
540 }
541
542 TEST(YCbCrInputTest, CombinedCbAndCr) {
543         const int width = 1;
544         const int height = 5;
545
546         // Pure-color test inputs, calculated with the formulas in Rec. 601
547         // section 2.5.4.
548         unsigned char y[width * height] = {
549                 16, 235, 81, 145, 41,
550         };
551         unsigned char cb_cr[width * height * 2] = {
552                 128, 128,
553                 128, 128,
554                  90, 240,
555                  54,  34,
556                 240, 110,
557         };
558         float expected_data[4 * width * height] = {
559                 0.0, 0.0, 0.0, 1.0,
560                 1.0, 1.0, 1.0, 1.0,
561                 1.0, 0.0, 0.0, 1.0,
562                 0.0, 1.0, 0.0, 1.0,
563                 0.0, 0.0, 1.0, 1.0,
564         };
565         float out_data[4 * width * height];
566
567         EffectChainTester tester(NULL, width, height);
568
569         ImageFormat format;
570         format.color_space = COLORSPACE_sRGB;
571         format.gamma_curve = GAMMA_sRGB;
572
573         YCbCrFormat ycbcr_format;
574         ycbcr_format.luma_coefficients = YCBCR_REC_601;
575         ycbcr_format.full_range = false;
576         ycbcr_format.num_levels = 256;
577         ycbcr_format.chroma_subsampling_x = 1;
578         ycbcr_format.chroma_subsampling_y = 1;
579         ycbcr_format.cb_x_position = 0.5f;
580         ycbcr_format.cb_y_position = 0.5f;
581         ycbcr_format.cr_x_position = 0.5f;
582         ycbcr_format.cr_y_position = 0.5f;
583
584         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_SPLIT_Y_AND_CBCR);
585         input->set_pixel_data(0, y);
586         input->set_pixel_data(1, cb_cr);
587         tester.get_chain()->add_input(input);
588
589         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
590
591         // Y'CbCr isn't 100% accurate (the input values are rounded),
592         // so we need some leeway.
593         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
594 }
595
596 TEST(YCbCrInputTest, ExternalTexture) {
597         const int width = 1;
598         const int height = 5;
599
600         // Pure-color test inputs, calculated with the formulas in Rec. 601
601         // section 2.5.4.
602         unsigned char y[width * height] = {
603                 16, 235, 81, 145, 41,
604         };
605         unsigned char cb[width * height] = {
606                 128, 128, 90, 54, 240,
607         };
608         unsigned char cr[width * height] = {
609                 128, 128, 240, 34, 110,
610         };
611         float expected_data[4 * width * height] = {
612                 0.0, 0.0, 0.0, 1.0,
613                 1.0, 1.0, 1.0, 1.0,
614                 1.0, 0.0, 0.0, 1.0,
615                 0.0, 1.0, 0.0, 1.0,
616                 0.0, 0.0, 1.0, 1.0,
617         };
618         float out_data[4 * width * height];
619
620         EffectChainTester tester(NULL, width, height);
621
622         ImageFormat format;
623         format.color_space = COLORSPACE_sRGB;
624         format.gamma_curve = GAMMA_sRGB;
625
626         YCbCrFormat ycbcr_format;
627         ycbcr_format.luma_coefficients = YCBCR_REC_601;
628         ycbcr_format.full_range = false;
629         ycbcr_format.num_levels = 256;
630         ycbcr_format.chroma_subsampling_x = 1;
631         ycbcr_format.chroma_subsampling_y = 1;
632         ycbcr_format.cb_x_position = 0.5f;
633         ycbcr_format.cb_y_position = 0.5f;
634         ycbcr_format.cr_x_position = 0.5f;
635         ycbcr_format.cr_y_position = 0.5f;
636
637         // Make a texture for the Cb data; keep the others as regular uploads.
638         ResourcePool pool;
639         GLuint cb_tex = pool.create_2d_texture(GL_R8, width, height);
640         check_error();
641         glBindTexture(GL_TEXTURE_2D, cb_tex);
642         check_error();
643         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
644         check_error();
645         glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
646         check_error();
647         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, cb);
648         check_error();
649         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
650         check_error();
651         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
652         check_error();
653
654         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
655         input->set_pixel_data(0, y);
656         input->set_texture_num(1, cb_tex);
657         input->set_pixel_data(2, cr);
658         tester.get_chain()->add_input(input);
659
660         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
661
662         pool.release_2d_texture(cb_tex);
663
664         // Y'CbCr isn't 100% accurate (the input values are rounded),
665         // so we need some leeway.
666         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
667 }
668
669 TEST(YCbCrTest, WikipediaRec601ForwardMatrix) {
670         YCbCrFormat ycbcr_format;
671         ycbcr_format.luma_coefficients = YCBCR_REC_601;
672         ycbcr_format.full_range = false;
673         ycbcr_format.num_levels = 256;
674
675         float offset[3];
676         Eigen::Matrix3d ycbcr_to_rgb;
677         compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
678
679         Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse() * 255.0;
680
681         // Values from https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion.
682         EXPECT_NEAR(  65.481, rgb_to_ycbcr(0,0), 1e-3);
683         EXPECT_NEAR( 128.553, rgb_to_ycbcr(0,1), 1e-3);
684         EXPECT_NEAR(  24.966, rgb_to_ycbcr(0,2), 1e-3);
685
686         EXPECT_NEAR( -37.797, rgb_to_ycbcr(1,0), 1e-3);
687         EXPECT_NEAR( -74.203, rgb_to_ycbcr(1,1), 1e-3);
688         EXPECT_NEAR( 112.000, rgb_to_ycbcr(1,2), 1e-3);
689
690         EXPECT_NEAR( 112.000, rgb_to_ycbcr(2,0), 1e-3);
691         EXPECT_NEAR( -93.786, rgb_to_ycbcr(2,1), 1e-3);
692         EXPECT_NEAR( -18.214, rgb_to_ycbcr(2,2), 1e-3);
693
694         EXPECT_NEAR( 16.0, offset[0] * 255.0, 1e-3);
695         EXPECT_NEAR(128.0, offset[1] * 255.0, 1e-3);
696         EXPECT_NEAR(128.0, offset[2] * 255.0, 1e-3);
697 }
698
699 TEST(YCbCrTest, WikipediaJPEGMatrices) {
700         YCbCrFormat ycbcr_format;
701         ycbcr_format.luma_coefficients = YCBCR_REC_601;
702         ycbcr_format.full_range = true;
703         ycbcr_format.num_levels = 256;
704
705         float offset[3];
706         Eigen::Matrix3d ycbcr_to_rgb;
707         compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
708
709         // Values from https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion.
710         EXPECT_NEAR( 1.00000, ycbcr_to_rgb(0,0), 1e-5);
711         EXPECT_NEAR( 0.00000, ycbcr_to_rgb(0,1), 1e-5);
712         EXPECT_NEAR( 1.40200, ycbcr_to_rgb(0,2), 1e-5);
713
714         EXPECT_NEAR( 1.00000, ycbcr_to_rgb(1,0), 1e-5);
715         EXPECT_NEAR(-0.34414, ycbcr_to_rgb(1,1), 1e-5);
716         EXPECT_NEAR(-0.71414, ycbcr_to_rgb(1,2), 1e-5);
717
718         EXPECT_NEAR( 1.00000, ycbcr_to_rgb(2,0), 1e-5);
719         EXPECT_NEAR( 1.77200, ycbcr_to_rgb(2,1), 1e-5);
720         EXPECT_NEAR( 0.00000, ycbcr_to_rgb(2,2), 1e-5);
721
722         Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse();
723
724         // Same.
725         EXPECT_NEAR( 0.299000, rgb_to_ycbcr(0,0), 1e-6);
726         EXPECT_NEAR( 0.587000, rgb_to_ycbcr(0,1), 1e-6);
727         EXPECT_NEAR( 0.114000, rgb_to_ycbcr(0,2), 1e-6);
728
729         EXPECT_NEAR(-0.168736, rgb_to_ycbcr(1,0), 1e-6);
730         EXPECT_NEAR(-0.331264, rgb_to_ycbcr(1,1), 1e-6);
731         EXPECT_NEAR( 0.500000, rgb_to_ycbcr(1,2), 1e-6);
732
733         EXPECT_NEAR( 0.500000, rgb_to_ycbcr(2,0), 1e-6);
734         EXPECT_NEAR(-0.418688, rgb_to_ycbcr(2,1), 1e-6);
735         EXPECT_NEAR(-0.081312, rgb_to_ycbcr(2,2), 1e-6);
736
737         EXPECT_NEAR(  0.0, offset[0] * 255.0, 1e-3);
738         EXPECT_NEAR(128.0, offset[1] * 255.0, 1e-3);
739         EXPECT_NEAR(128.0, offset[2] * 255.0, 1e-3);
740 }
741
742 TEST(YCbCrTest, BlackmagicForwardMatrix) {
743         YCbCrFormat ycbcr_format;
744         ycbcr_format.luma_coefficients = YCBCR_REC_709;
745         ycbcr_format.full_range = false;
746         ycbcr_format.num_levels = 256;
747
748         float offset[3];
749         Eigen::Matrix3d ycbcr_to_rgb;
750         compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
751
752         Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse();
753
754         // Values from DeckLink SDK documentation.
755         EXPECT_NEAR( 0.183, rgb_to_ycbcr(0,0), 1e-3);
756         EXPECT_NEAR( 0.614, rgb_to_ycbcr(0,1), 1e-3);
757         EXPECT_NEAR( 0.062, rgb_to_ycbcr(0,2), 1e-3);
758
759         EXPECT_NEAR(-0.101, rgb_to_ycbcr(1,0), 1e-3);
760         EXPECT_NEAR(-0.338, rgb_to_ycbcr(1,1), 1e-3);
761         EXPECT_NEAR( 0.439, rgb_to_ycbcr(1,2), 1e-3);
762
763         EXPECT_NEAR( 0.439, rgb_to_ycbcr(2,0), 1e-3);
764         EXPECT_NEAR(-0.399, rgb_to_ycbcr(2,1), 1e-3);
765         EXPECT_NEAR(-0.040, rgb_to_ycbcr(2,2), 1e-3);
766
767         EXPECT_NEAR( 16.0, offset[0] * 255.0, 1e-3);
768         EXPECT_NEAR(128.0, offset[1] * 255.0, 1e-3);
769         EXPECT_NEAR(128.0, offset[2] * 255.0, 1e-3);
770 }
771
772 TEST(YCbCrInputTest, NoData) {
773         const int width = 1;
774         const int height = 5;
775
776         float out_data[4 * width * height];
777
778         EffectChainTester tester(NULL, width, height);
779
780         ImageFormat format;
781         format.color_space = COLORSPACE_sRGB;
782         format.gamma_curve = GAMMA_sRGB;
783
784         YCbCrFormat ycbcr_format;
785         ycbcr_format.luma_coefficients = YCBCR_REC_601;
786         ycbcr_format.full_range = false;
787         ycbcr_format.num_levels = 256;
788         ycbcr_format.chroma_subsampling_x = 1;
789         ycbcr_format.chroma_subsampling_y = 1;
790         ycbcr_format.cb_x_position = 0.5f;
791         ycbcr_format.cb_y_position = 0.5f;
792         ycbcr_format.cr_x_position = 0.5f;
793         ycbcr_format.cr_y_position = 0.5f;
794
795         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
796         tester.get_chain()->add_input(input);
797
798         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
799
800         // Don't care what the output was, just that it does not crash.
801 }
802
803 TEST(YCbCrInputTest, TenBitInterleaved) {
804         const int width = 1;
805         const int height = 5;
806
807         // Pure-color inputs, calculated using formulas 3.2, 3.3 and 3.4 from
808         // Rec. 709. (Except the first two, which are obvious given the 64–940
809         // range of luminance.)
810         unsigned expanded_data[width * height * 3] = {
811                  64, 512, 512,
812                 940, 512, 512,
813                 250, 409, 960,
814                 691, 167, 105,
815                 127, 960, 471,
816         };
817         float expected_data[4 * width * height] = {
818                 0.0, 0.0, 0.0, 1.0,
819                 1.0, 1.0, 1.0, 1.0,
820                 1.0, 0.0, 0.0, 1.0,
821                 0.0, 1.0, 0.0, 1.0,
822                 0.0, 0.0, 1.0, 1.0,
823         };
824         float out_data[4 * width * height];
825
826         // Pack 32:32:32 to 10:10:10:2.
827         uint32_t data[width * height];
828         for (unsigned i = 0; i < width * height; ++i) {
829                 data[i] =
830                          expanded_data[i * 3 + 0]        |
831                         (expanded_data[i * 3 + 1] << 10) |
832                         (expanded_data[i * 3 + 2] << 20);
833         }
834
835         EffectChainTester tester(NULL, width, height);
836
837         ImageFormat format;
838         format.color_space = COLORSPACE_sRGB;
839         format.gamma_curve = GAMMA_sRGB;
840
841         YCbCrFormat ycbcr_format;
842         ycbcr_format.luma_coefficients = YCBCR_REC_709;
843         ycbcr_format.full_range = false;
844         ycbcr_format.num_levels = 1024;  // 10-bit.
845         ycbcr_format.chroma_subsampling_x = 1;
846         ycbcr_format.chroma_subsampling_y = 1;
847         ycbcr_format.cb_x_position = 0.5f;
848         ycbcr_format.cb_y_position = 0.5f;
849         ycbcr_format.cr_x_position = 0.5f;
850         ycbcr_format.cr_y_position = 0.5f;
851
852         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_INTERLEAVED, GL_UNSIGNED_INT_2_10_10_10_REV);
853         input->set_pixel_data(0, data);
854         tester.get_chain()->add_input(input);
855
856         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
857
858         // We can set much tighter limits on this than 8-bit Y'CbCr;
859         // even tighter than the default limits.
860         expect_equal(expected_data, out_data, 4 * width, height, 0.002, 0.0003);
861 }
862
863 TEST(YCbCrInputTest, TenBitPlanar) {
864         const int width = 1;
865         const int height = 5;
866
867         // The same data as TenBitInterleaved, but split.
868         uint16_t y[width * height] = {
869                  64,
870                 940,
871                 250,
872                 691,
873                 127,
874         };
875         uint16_t cb[width * height] = {
876                 512,
877                 512,
878                 409,
879                 167,
880                 960,
881         };
882         uint16_t cr[width * height] = {
883                 512,
884                 512,
885                 960,
886                 105,
887                 471,
888         };
889         float expected_data[4 * width * height] = {
890                 0.0, 0.0, 0.0, 1.0,
891                 1.0, 1.0, 1.0, 1.0,
892                 1.0, 0.0, 0.0, 1.0,
893                 0.0, 1.0, 0.0, 1.0,
894                 0.0, 0.0, 1.0, 1.0,
895         };
896         float out_data[4 * width * height];
897
898         EffectChainTester tester(NULL, width, height);
899
900         ImageFormat format;
901         format.color_space = COLORSPACE_sRGB;
902         format.gamma_curve = GAMMA_sRGB;
903
904         YCbCrFormat ycbcr_format;
905         ycbcr_format.luma_coefficients = YCBCR_REC_709;
906         ycbcr_format.full_range = false;
907         ycbcr_format.num_levels = 1024;  // 10-bit.
908         ycbcr_format.chroma_subsampling_x = 1;
909         ycbcr_format.chroma_subsampling_y = 1;
910         ycbcr_format.cb_x_position = 0.5f;
911         ycbcr_format.cb_y_position = 0.5f;
912         ycbcr_format.cr_x_position = 0.5f;
913         ycbcr_format.cr_y_position = 0.5f;
914
915         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_PLANAR, GL_UNSIGNED_SHORT);
916         input->set_pixel_data(0, y);
917         input->set_pixel_data(1, cb);
918         input->set_pixel_data(2, cr);
919         tester.get_chain()->add_input(input);
920
921         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
922
923         // We can set much tighter limits on this than 8-bit Y'CbCr;
924         // even tighter than the default limits.
925         expect_equal(expected_data, out_data, 4 * width, height, 0.002, 0.0003);
926 }
927
928 // Effectively scales down its input linearly by 4x (and repeating it),
929 // which is not attainable without mipmaps.
930 class MipmapNeedingEffect : public Effect {
931 public:
932         MipmapNeedingEffect() {}
933         virtual bool needs_mipmaps() const { return true; }
934
935         // To be allowed to mess with the sampler state.
936         virtual bool needs_texture_bounce() const { return true; }
937
938         virtual string effect_type_id() const { return "MipmapNeedingEffect"; }
939         string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
940         virtual void inform_added(EffectChain *chain) { this->chain = chain; }
941
942         void set_gl_state(GLuint glsl_program_num, const string& prefix, unsigned *sampler_num)
943         {
944                 Node *self = chain->find_node_for_effect(this);
945                 glActiveTexture(chain->get_input_sampler(self, 0));
946                 check_error();
947                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
948                 check_error();
949                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
950                 check_error();
951         }
952
953 private:
954         EffectChain *chain;
955 };
956
957 // Basically the same test as EffectChainTest_MipmapGenerationWorks,
958 // just with the data converted to Y'CbCr (as red only).
959 TEST(EffectChainTest, MipmapGenerationWorks) {
960         unsigned width = 4;
961         unsigned height = 16;
962         float red_data[width * height] = {  // In 4x4 blocks.
963                 1.0f, 0.0f, 0.0f, 0.0f,
964                 0.0f, 0.0f, 0.0f, 0.0f,
965                 0.0f, 0.0f, 0.0f, 0.0f,
966                 0.0f, 0.0f, 0.0f, 1.0f,
967
968                 0.0f, 0.0f, 0.0f, 0.0f,
969                 0.0f, 0.5f, 0.0f, 0.0f,
970                 0.0f, 0.0f, 1.0f, 0.0f,
971                 0.0f, 0.0f, 0.0f, 0.0f,
972
973                 1.0f, 1.0f, 1.0f, 1.0f,
974                 1.0f, 1.0f, 1.0f, 1.0f,
975                 1.0f, 1.0f, 1.0f, 1.0f,
976                 1.0f, 1.0f, 1.0f, 1.0f,
977
978                 0.0f, 0.0f, 0.0f, 0.0f,
979                 0.0f, 1.0f, 1.0f, 0.0f,
980                 0.0f, 1.0f, 1.0f, 0.0f,
981                 0.0f, 0.0f, 0.0f, 0.0f,
982         };
983         float expected_data[width * height] = {  // Repeated four times each way.
984                 0.125f,   0.125f,   0.125f,   0.125f,
985                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
986                 1.0f,     1.0f,     1.0f,     1.0f,
987                 0.25f,    0.25f,    0.25f,    0.25f,
988
989                 0.125f,   0.125f,   0.125f,   0.125f,
990                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
991                 1.0f,     1.0f,     1.0f,     1.0f,
992                 0.25f,    0.25f,    0.25f,    0.25f,
993
994                 0.125f,   0.125f,   0.125f,   0.125f,
995                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
996                 1.0f,     1.0f,     1.0f,     1.0f,
997                 0.25f,    0.25f,    0.25f,    0.25f,
998
999                 0.125f,   0.125f,   0.125f,   0.125f,
1000                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
1001                 1.0f,     1.0f,     1.0f,     1.0f,
1002                 0.25f,    0.25f,    0.25f,    0.25f,
1003         };
1004         float expected_data_rgba[width * height * 4];
1005         unsigned char ycbcr_data[width * height * 3];
1006
1007         // Convert to Y'CbCr.
1008         YCbCrFormat ycbcr_format;
1009         ycbcr_format.luma_coefficients = YCBCR_REC_709;
1010         ycbcr_format.full_range = false;
1011         ycbcr_format.num_levels = 256;
1012         ycbcr_format.chroma_subsampling_x = 1;
1013         ycbcr_format.chroma_subsampling_y = 1;
1014         ycbcr_format.cb_x_position = 0.5f;
1015         ycbcr_format.cb_y_position = 0.5f;
1016         ycbcr_format.cr_x_position = 0.5f;
1017         ycbcr_format.cr_y_position = 0.5f;
1018
1019         float offset[3];
1020         Eigen::Matrix3d ycbcr_to_rgb;
1021         compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
1022
1023         Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse();
1024         for (unsigned i = 0; i < 64; ++i) {
1025                 Eigen::Vector3d rgb(red_data[i], 0.0, 0.0);
1026                 Eigen::Vector3d ycbcr = rgb_to_ycbcr * rgb;
1027                 ycbcr(0) += offset[0];
1028                 ycbcr(1) += offset[1];
1029                 ycbcr(2) += offset[2];
1030                 ycbcr_data[i * 3 + 0] = lrintf(ycbcr(0) * 255.0);
1031                 ycbcr_data[i * 3 + 1] = lrintf(ycbcr(1) * 255.0);
1032                 ycbcr_data[i * 3 + 2] = lrintf(ycbcr(2) * 255.0);
1033         }
1034
1035         // Expand expected_data to RGBA.
1036         for (unsigned i = 0; i < 64; ++i) {
1037                 expected_data_rgba[i * 4 + 0] = expected_data[i];
1038                 expected_data_rgba[i * 4 + 1] = 0.0f;
1039                 expected_data_rgba[i * 4 + 2] = 0.0f;
1040                 expected_data_rgba[i * 4 + 3] = 1.0f;
1041         }
1042
1043         ImageFormat format;
1044         format.color_space = COLORSPACE_sRGB;
1045         format.gamma_curve = GAMMA_sRGB;
1046
1047         float out_data[width * height * 4];
1048         EffectChainTester tester(NULL, width, height);
1049         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_INTERLEAVED);
1050         input->set_pixel_data(0, ycbcr_data);
1051         tester.get_chain()->add_input(input);
1052         tester.get_chain()->add_effect(new MipmapNeedingEffect());
1053         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
1054
1055         // The usual pretty loose limits.
1056         expect_equal(expected_data_rgba, out_data, width * 4, height, 0.025, 0.002);
1057 }
1058
1059 }  // namespace movit