1 // Unit tests for YCbCrInput. Also tests the matrix functions in ycbcr.cpp directly.
9 #include "effect_chain.h"
10 #include "gtest/gtest.h"
11 #include "test_util.h"
13 #include "resource_pool.h"
14 #include "ycbcr_input.h"
20 TEST(YCbCrInputTest, Simple444) {
24 // Pure-color test inputs, calculated with the formulas in Rec. 601
26 unsigned char y[width * height] = {
29 unsigned char cb[width * height] = {
30 128, 128, 90, 54, 240,
32 unsigned char cr[width * height] = {
33 128, 128, 240, 34, 110,
35 float expected_data[4 * width * height] = {
42 float out_data[4 * width * height];
44 EffectChainTester tester(nullptr, width, height);
47 format.color_space = COLORSPACE_sRGB;
48 format.gamma_curve = GAMMA_sRGB;
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;
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);
67 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
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);
74 TEST(YCbCrInputTest, Interleaved444) {
78 // Same data as Simple444, just rearranged.
79 unsigned char data[width * height * 3] = {
86 float expected_data[4 * width * height] = {
93 float out_data[4 * width * height];
95 EffectChainTester tester(nullptr, width, height);
98 format.color_space = COLORSPACE_sRGB;
99 format.gamma_curve = GAMMA_sRGB;
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;
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);
116 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
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);
123 TEST(YCbCrInputTest, FullRangeRec601) {
125 const int height = 5;
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] = {
133 unsigned char cb[width * height] = {
134 128, 128, 85, 44, 255,
136 unsigned char cr[width * height] = {
137 128, 128, 255, 21, 107,
139 float expected_data[4 * width * height] = {
146 float out_data[4 * width * height];
148 EffectChainTester tester(nullptr, width, height);
151 format.color_space = COLORSPACE_sRGB;
152 format.gamma_curve = GAMMA_sRGB;
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;
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);
171 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
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);
178 TEST(YCbCrInputTest, Rec709) {
180 const int height = 5;
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,
187 unsigned char cb[width * height] = {
188 128, 128, 102, 42, 240,
190 unsigned char cr[width * height] = {
191 128, 128, 240, 26, 118,
193 float expected_data[4 * width * height] = {
200 float out_data[4 * width * height];
202 EffectChainTester tester(nullptr, width, height);
205 format.color_space = COLORSPACE_sRGB;
206 format.gamma_curve = GAMMA_sRGB;
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;
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);
225 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
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);
232 TEST(YCbCrInputTest, Rec2020) {
234 const int height = 5;
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,
243 unsigned char cb[width * height] = {
244 128, 128, 97, 47, 240,
246 unsigned char cr[width * height] = {
247 128, 128, 240, 25, 119,
249 float expected_data[4 * width * height] = {
256 float out_data[4 * width * height];
258 EffectChainTester tester(nullptr, width, height);
261 format.color_space = COLORSPACE_sRGB;
262 format.gamma_curve = GAMMA_sRGB;
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;
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);
281 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
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);
288 // Very similar to Rec709.
289 TEST(YCbCrInputTest, ChangeFormat) {
291 const int height = 5;
293 // Pure-color test inputs, calculated with the formulas in Rec. 709
294 // page 19, items 3.4 and 3.5.
295 unsigned char y[width * height] = {
296 16, 235, 63, 173, 32,
298 unsigned char cb[width * height] = {
299 128, 128, 102, 42, 240,
301 unsigned char cr[width * height] = {
302 128, 128, 240, 26, 118,
304 float expected_data[4 * width * height] = {
311 float out_data[4 * width * height];
313 EffectChainTester tester(nullptr, width, height);
316 format.color_space = COLORSPACE_sRGB;
317 format.gamma_curve = GAMMA_sRGB;
319 // Basically all of these values will be changed after finalize.
320 YCbCrFormat initial_ycbcr_format;
321 initial_ycbcr_format.luma_coefficients = YCBCR_REC_601;
322 initial_ycbcr_format.full_range = true;
323 initial_ycbcr_format.num_levels = 1024;
324 initial_ycbcr_format.chroma_subsampling_x = 1;
325 initial_ycbcr_format.chroma_subsampling_y = 5;
326 initial_ycbcr_format.cb_x_position = 0.0f;
327 initial_ycbcr_format.cb_y_position = 0.5f;
328 initial_ycbcr_format.cr_x_position = 0.0f;
329 initial_ycbcr_format.cr_y_position = 0.5f;
331 YCbCrInput *input = new YCbCrInput(format, initial_ycbcr_format, width, height);
332 input->set_pixel_data(0, y);
333 input->set_pixel_data(1, cb);
334 input->set_pixel_data(2, cr);
335 tester.get_chain()->add_input(input);
337 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
339 // Rerun with the right format.
340 YCbCrFormat ycbcr_format;
341 ycbcr_format.luma_coefficients = YCBCR_REC_709;
342 ycbcr_format.full_range = false;
343 ycbcr_format.num_levels = 256;
344 ycbcr_format.chroma_subsampling_x = 1;
345 ycbcr_format.chroma_subsampling_y = 1;
346 ycbcr_format.cb_x_position = 0.5f;
347 ycbcr_format.cb_y_position = 0.5f;
348 ycbcr_format.cr_x_position = 0.5f;
349 ycbcr_format.cr_y_position = 0.5f;
351 input->change_ycbcr_format(ycbcr_format);
352 input->set_width(width);
353 input->set_height(height);
355 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
357 // Y'CbCr isn't 100% accurate (the input values are rounded),
358 // so we need some leeway.
359 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
362 TEST(YCbCrInputTest, Subsampling420) {
364 const int height = 4;
366 unsigned char y[width * height] = {
372 unsigned char cb[(width/2) * (height/2)] = {
376 unsigned char cr[(width/2) * (height/2)] = {
381 // Note: This is only the blue channel. The chroma samples (with associated
382 // values for blue) are marked off in comments.
383 float expected_data[width * height] = {
384 0.000, 0.125, 0.375, 0.500,
386 0.125, 0.250, 0.500, 0.625,
388 0.375, 0.500, 0.750, 0.875,
390 0.500, 0.625, 0.875, 1.000,
392 float out_data[width * height];
394 EffectChainTester tester(nullptr, width, height);
397 format.color_space = COLORSPACE_sRGB;
398 format.gamma_curve = GAMMA_sRGB;
400 YCbCrFormat ycbcr_format;
401 ycbcr_format.luma_coefficients = YCBCR_REC_601;
402 ycbcr_format.full_range = false;
403 ycbcr_format.num_levels = 256;
404 ycbcr_format.chroma_subsampling_x = 2;
405 ycbcr_format.chroma_subsampling_y = 2;
406 ycbcr_format.cb_x_position = 0.5f;
407 ycbcr_format.cb_y_position = 0.5f;
408 ycbcr_format.cr_x_position = 0.5f;
409 ycbcr_format.cr_y_position = 0.5f;
411 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
412 input->set_pixel_data(0, y);
413 input->set_pixel_data(1, cb);
414 input->set_pixel_data(2, cr);
415 tester.get_chain()->add_input(input);
417 tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
419 // Y'CbCr isn't 100% accurate (the input values are rounded),
420 // so we need some leeway.
421 expect_equal(expected_data, out_data, width, height, 0.01, 0.001);
424 TEST(YCbCrInputTest, Subsampling420WithNonCenteredSamples) {
426 const int height = 4;
428 unsigned char y[width * height] = {
434 unsigned char cb[(width/2) * (height/2)] = {
438 unsigned char cr[(width/2) * (height/2)] = {
443 // Note: This is only the blue channel. The chroma samples (with associated
444 // values for blue) are marked off in comments.
445 float expected_data[width * height] = {
446 0.000, 0.250, 0.500, 0.500,
448 0.125, 0.375, 0.625, 0.625,
450 0.375, 0.625, 0.875, 0.875,
452 0.500, 0.750, 1.000, 1.000,
454 float out_data[width * height];
456 EffectChainTester tester(nullptr, width, height);
459 format.color_space = COLORSPACE_sRGB;
460 format.gamma_curve = GAMMA_sRGB;
462 YCbCrFormat ycbcr_format;
463 ycbcr_format.luma_coefficients = YCBCR_REC_601;
464 ycbcr_format.full_range = false;
465 ycbcr_format.num_levels = 256;
466 ycbcr_format.chroma_subsampling_x = 2;
467 ycbcr_format.chroma_subsampling_y = 2;
468 ycbcr_format.cb_x_position = 0.0f;
469 ycbcr_format.cb_y_position = 0.5f;
470 ycbcr_format.cr_x_position = 0.0f;
471 ycbcr_format.cr_y_position = 0.5f;
473 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
474 input->set_pixel_data(0, y);
475 input->set_pixel_data(1, cb);
476 input->set_pixel_data(2, cr);
477 tester.get_chain()->add_input(input);
479 tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
481 // Y'CbCr isn't 100% accurate (the input values are rounded),
482 // so we need some leeway.
483 expect_equal(expected_data, out_data, width, height, 0.01, 0.0012);
486 // Yes, some 4:2:2 formats actually have this craziness.
487 TEST(YCbCrInputTest, DifferentCbAndCrPositioning) {
489 const int height = 4;
491 unsigned char y[width * height] = {
497 unsigned char cb[(width/2) * height] = {
503 unsigned char cr[(width/2) * height] = {
510 // Chroma samples in this csae are always co-sited with a luma sample;
511 // their associated color values and position are marked off in comments.
512 float expected_data_blue[width * height] = {
513 0.000 /* 0.0 */, 0.250, 0.500 /* 0.5 */, 0.500,
514 0.500 /* 0.5 */, 0.750, 1.000 /* 1.0 */, 1.000,
515 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */, 0.500,
516 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */, 0.500,
518 float expected_data_red[width * height] = {
519 0.000, 0.000 /* 0.0 */, 0.250, 0.500 /* 0.5 */,
520 0.500, 0.500 /* 0.5 */, 0.750, 1.000 /* 1.0 */,
521 0.500, 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */,
522 0.500, 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */,
524 float out_data[width * height];
526 EffectChainTester tester(nullptr, width, height);
529 format.color_space = COLORSPACE_sRGB;
530 format.gamma_curve = GAMMA_sRGB;
532 YCbCrFormat ycbcr_format;
533 ycbcr_format.luma_coefficients = YCBCR_REC_601;
534 ycbcr_format.full_range = false;
535 ycbcr_format.num_levels = 256;
536 ycbcr_format.chroma_subsampling_x = 2;
537 ycbcr_format.chroma_subsampling_y = 1;
538 ycbcr_format.cb_x_position = 0.0f;
539 ycbcr_format.cb_y_position = 0.5f;
540 ycbcr_format.cr_x_position = 1.0f;
541 ycbcr_format.cr_y_position = 0.5f;
543 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
544 input->set_pixel_data(0, y);
545 input->set_pixel_data(1, cb);
546 input->set_pixel_data(2, cr);
547 tester.get_chain()->add_input(input);
549 // Y'CbCr isn't 100% accurate (the input values are rounded),
550 // so we need some leeway.
551 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
552 expect_equal(expected_data_red, out_data, width, height, 0.02, 0.002);
554 tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
555 expect_equal(expected_data_blue, out_data, width, height, 0.01, 0.001);
558 TEST(YCbCrInputTest, PBO) {
560 const int height = 5;
562 // Pure-color test inputs, calculated with the formulas in Rec. 601
564 unsigned char data[width * height * 3] = {
565 16, 235, 81, 145, 41,
566 128, 128, 90, 54, 240,
567 128, 128, 240, 34, 110,
569 float expected_data[4 * width * height] = {
576 float out_data[4 * width * height];
579 glGenBuffers(1, &pbo);
580 glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo);
581 glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, width * height * 3, data, GL_STREAM_DRAW);
582 glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
584 EffectChainTester tester(nullptr, width, height);
587 format.color_space = COLORSPACE_sRGB;
588 format.gamma_curve = GAMMA_sRGB;
590 YCbCrFormat ycbcr_format;
591 ycbcr_format.luma_coefficients = YCBCR_REC_601;
592 ycbcr_format.full_range = false;
593 ycbcr_format.num_levels = 256;
594 ycbcr_format.chroma_subsampling_x = 1;
595 ycbcr_format.chroma_subsampling_y = 1;
596 ycbcr_format.cb_x_position = 0.5f;
597 ycbcr_format.cb_y_position = 0.5f;
598 ycbcr_format.cr_x_position = 0.5f;
599 ycbcr_format.cr_y_position = 0.5f;
601 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
602 input->set_pixel_data(0, (unsigned char *)BUFFER_OFFSET(0), pbo);
603 input->set_pixel_data(1, (unsigned char *)BUFFER_OFFSET(width * height), pbo);
604 input->set_pixel_data(2, (unsigned char *)BUFFER_OFFSET(width * height * 2), pbo);
605 tester.get_chain()->add_input(input);
607 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
609 // Y'CbCr isn't 100% accurate (the input values are rounded),
610 // so we need some leeway.
611 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
613 glDeleteBuffers(1, &pbo);
616 TEST(YCbCrInputTest, CombinedCbAndCr) {
618 const int height = 5;
620 // Pure-color test inputs, calculated with the formulas in Rec. 601
622 unsigned char y[width * height] = {
623 16, 235, 81, 145, 41,
625 unsigned char cb_cr[width * height * 2] = {
632 float expected_data[4 * width * height] = {
639 float out_data[4 * width * height];
641 EffectChainTester tester(nullptr, width, height);
644 format.color_space = COLORSPACE_sRGB;
645 format.gamma_curve = GAMMA_sRGB;
647 YCbCrFormat ycbcr_format;
648 ycbcr_format.luma_coefficients = YCBCR_REC_601;
649 ycbcr_format.full_range = false;
650 ycbcr_format.num_levels = 256;
651 ycbcr_format.chroma_subsampling_x = 1;
652 ycbcr_format.chroma_subsampling_y = 1;
653 ycbcr_format.cb_x_position = 0.5f;
654 ycbcr_format.cb_y_position = 0.5f;
655 ycbcr_format.cr_x_position = 0.5f;
656 ycbcr_format.cr_y_position = 0.5f;
658 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_SPLIT_Y_AND_CBCR);
659 input->set_pixel_data(0, y);
660 input->set_pixel_data(1, cb_cr);
661 tester.get_chain()->add_input(input);
663 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
665 // Y'CbCr isn't 100% accurate (the input values are rounded),
666 // so we need some leeway.
667 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
670 TEST(YCbCrInputTest, ExternalTexture) {
672 const int height = 5;
674 // Pure-color test inputs, calculated with the formulas in Rec. 601
676 unsigned char y[width * height] = {
677 16, 235, 81, 145, 41,
679 unsigned char cb[width * height] = {
680 128, 128, 90, 54, 240,
682 unsigned char cr[width * height] = {
683 128, 128, 240, 34, 110,
685 float expected_data[4 * width * height] = {
692 float out_data[4 * width * height];
694 EffectChainTester tester(nullptr, width, height);
697 format.color_space = COLORSPACE_sRGB;
698 format.gamma_curve = GAMMA_sRGB;
700 YCbCrFormat ycbcr_format;
701 ycbcr_format.luma_coefficients = YCBCR_REC_601;
702 ycbcr_format.full_range = false;
703 ycbcr_format.num_levels = 256;
704 ycbcr_format.chroma_subsampling_x = 1;
705 ycbcr_format.chroma_subsampling_y = 1;
706 ycbcr_format.cb_x_position = 0.5f;
707 ycbcr_format.cb_y_position = 0.5f;
708 ycbcr_format.cr_x_position = 0.5f;
709 ycbcr_format.cr_y_position = 0.5f;
711 // Make a texture for the Cb data; keep the others as regular uploads.
713 GLuint cb_tex = pool.create_2d_texture(GL_R8, width, height);
715 glBindTexture(GL_TEXTURE_2D, cb_tex);
717 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
719 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
721 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, cb);
723 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
725 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
728 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
729 input->set_pixel_data(0, y);
730 input->set_texture_num(1, cb_tex);
731 input->set_pixel_data(2, cr);
732 tester.get_chain()->add_input(input);
734 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
736 pool.release_2d_texture(cb_tex);
738 // Y'CbCr isn't 100% accurate (the input values are rounded),
739 // so we need some leeway.
740 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
743 TEST(YCbCrTest, WikipediaRec601ForwardMatrix) {
744 YCbCrFormat ycbcr_format;
745 ycbcr_format.luma_coefficients = YCBCR_REC_601;
746 ycbcr_format.full_range = false;
747 ycbcr_format.num_levels = 256;
750 Eigen::Matrix3d ycbcr_to_rgb;
751 compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
753 Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse() * 255.0;
755 // Values from https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion.
756 EXPECT_NEAR( 65.481, rgb_to_ycbcr(0,0), 1e-3);
757 EXPECT_NEAR( 128.553, rgb_to_ycbcr(0,1), 1e-3);
758 EXPECT_NEAR( 24.966, rgb_to_ycbcr(0,2), 1e-3);
760 EXPECT_NEAR( -37.797, rgb_to_ycbcr(1,0), 1e-3);
761 EXPECT_NEAR( -74.203, rgb_to_ycbcr(1,1), 1e-3);
762 EXPECT_NEAR( 112.000, rgb_to_ycbcr(1,2), 1e-3);
764 EXPECT_NEAR( 112.000, rgb_to_ycbcr(2,0), 1e-3);
765 EXPECT_NEAR( -93.786, rgb_to_ycbcr(2,1), 1e-3);
766 EXPECT_NEAR( -18.214, rgb_to_ycbcr(2,2), 1e-3);
768 EXPECT_NEAR( 16.0, offset[0] * 255.0, 1e-3);
769 EXPECT_NEAR(128.0, offset[1] * 255.0, 1e-3);
770 EXPECT_NEAR(128.0, offset[2] * 255.0, 1e-3);
773 TEST(YCbCrTest, WikipediaJPEGMatrices) {
774 YCbCrFormat ycbcr_format;
775 ycbcr_format.luma_coefficients = YCBCR_REC_601;
776 ycbcr_format.full_range = true;
777 ycbcr_format.num_levels = 256;
780 Eigen::Matrix3d ycbcr_to_rgb;
781 compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
783 // Values from https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion.
784 EXPECT_NEAR( 1.00000, ycbcr_to_rgb(0,0), 1e-5);
785 EXPECT_NEAR( 0.00000, ycbcr_to_rgb(0,1), 1e-5);
786 EXPECT_NEAR( 1.40200, ycbcr_to_rgb(0,2), 1e-5);
788 EXPECT_NEAR( 1.00000, ycbcr_to_rgb(1,0), 1e-5);
789 EXPECT_NEAR(-0.34414, ycbcr_to_rgb(1,1), 1e-5);
790 EXPECT_NEAR(-0.71414, ycbcr_to_rgb(1,2), 1e-5);
792 EXPECT_NEAR( 1.00000, ycbcr_to_rgb(2,0), 1e-5);
793 EXPECT_NEAR( 1.77200, ycbcr_to_rgb(2,1), 1e-5);
794 EXPECT_NEAR( 0.00000, ycbcr_to_rgb(2,2), 1e-5);
796 Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse();
799 EXPECT_NEAR( 0.299000, rgb_to_ycbcr(0,0), 1e-6);
800 EXPECT_NEAR( 0.587000, rgb_to_ycbcr(0,1), 1e-6);
801 EXPECT_NEAR( 0.114000, rgb_to_ycbcr(0,2), 1e-6);
803 EXPECT_NEAR(-0.168736, rgb_to_ycbcr(1,0), 1e-6);
804 EXPECT_NEAR(-0.331264, rgb_to_ycbcr(1,1), 1e-6);
805 EXPECT_NEAR( 0.500000, rgb_to_ycbcr(1,2), 1e-6);
807 EXPECT_NEAR( 0.500000, rgb_to_ycbcr(2,0), 1e-6);
808 EXPECT_NEAR(-0.418688, rgb_to_ycbcr(2,1), 1e-6);
809 EXPECT_NEAR(-0.081312, rgb_to_ycbcr(2,2), 1e-6);
811 EXPECT_NEAR( 0.0, offset[0] * 255.0, 1e-3);
812 EXPECT_NEAR(128.0, offset[1] * 255.0, 1e-3);
813 EXPECT_NEAR(128.0, offset[2] * 255.0, 1e-3);
816 TEST(YCbCrTest, BlackmagicForwardMatrix) {
817 YCbCrFormat ycbcr_format;
818 ycbcr_format.luma_coefficients = YCBCR_REC_709;
819 ycbcr_format.full_range = false;
820 ycbcr_format.num_levels = 256;
823 Eigen::Matrix3d ycbcr_to_rgb;
824 compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
826 Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse();
828 // Values from DeckLink SDK documentation.
829 EXPECT_NEAR( 0.183, rgb_to_ycbcr(0,0), 1e-3);
830 EXPECT_NEAR( 0.614, rgb_to_ycbcr(0,1), 1e-3);
831 EXPECT_NEAR( 0.062, rgb_to_ycbcr(0,2), 1e-3);
833 EXPECT_NEAR(-0.101, rgb_to_ycbcr(1,0), 1e-3);
834 EXPECT_NEAR(-0.338, rgb_to_ycbcr(1,1), 1e-3);
835 EXPECT_NEAR( 0.439, rgb_to_ycbcr(1,2), 1e-3);
837 EXPECT_NEAR( 0.439, rgb_to_ycbcr(2,0), 1e-3);
838 EXPECT_NEAR(-0.399, rgb_to_ycbcr(2,1), 1e-3);
839 EXPECT_NEAR(-0.040, rgb_to_ycbcr(2,2), 1e-3);
841 EXPECT_NEAR( 16.0, offset[0] * 255.0, 1e-3);
842 EXPECT_NEAR(128.0, offset[1] * 255.0, 1e-3);
843 EXPECT_NEAR(128.0, offset[2] * 255.0, 1e-3);
846 TEST(YCbCrInputTest, NoData) {
848 const int height = 5;
850 float out_data[4 * width * height];
852 EffectChainTester tester(nullptr, width, height);
855 format.color_space = COLORSPACE_sRGB;
856 format.gamma_curve = GAMMA_sRGB;
858 YCbCrFormat ycbcr_format;
859 ycbcr_format.luma_coefficients = YCBCR_REC_601;
860 ycbcr_format.full_range = false;
861 ycbcr_format.num_levels = 256;
862 ycbcr_format.chroma_subsampling_x = 1;
863 ycbcr_format.chroma_subsampling_y = 1;
864 ycbcr_format.cb_x_position = 0.5f;
865 ycbcr_format.cb_y_position = 0.5f;
866 ycbcr_format.cr_x_position = 0.5f;
867 ycbcr_format.cr_y_position = 0.5f;
869 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
870 tester.get_chain()->add_input(input);
872 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
874 // Don't care what the output was, just that it does not crash.
877 TEST(YCbCrInputTest, TenBitInterleaved) {
879 const int height = 5;
881 // Pure-color inputs, calculated using formulas 3.2, 3.3 and 3.4 from
882 // Rec. 709. (Except the first two, which are obvious given the 64–940
883 // range of luminance.)
884 unsigned expanded_data[width * height * 3] = {
891 float expected_data[4 * width * height] = {
898 float out_data[4 * width * height];
900 // Pack 32:32:32 to 10:10:10:2.
901 uint32_t data[width * height];
902 for (unsigned i = 0; i < width * height; ++i) {
904 expanded_data[i * 3 + 0] |
905 (expanded_data[i * 3 + 1] << 10) |
906 (expanded_data[i * 3 + 2] << 20);
909 EffectChainTester tester(nullptr, width, height);
912 format.color_space = COLORSPACE_sRGB;
913 format.gamma_curve = GAMMA_sRGB;
915 YCbCrFormat ycbcr_format;
916 ycbcr_format.luma_coefficients = YCBCR_REC_709;
917 ycbcr_format.full_range = false;
918 ycbcr_format.num_levels = 1024; // 10-bit.
919 ycbcr_format.chroma_subsampling_x = 1;
920 ycbcr_format.chroma_subsampling_y = 1;
921 ycbcr_format.cb_x_position = 0.5f;
922 ycbcr_format.cb_y_position = 0.5f;
923 ycbcr_format.cr_x_position = 0.5f;
924 ycbcr_format.cr_y_position = 0.5f;
926 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_INTERLEAVED, GL_UNSIGNED_INT_2_10_10_10_REV);
927 input->set_pixel_data(0, data);
928 tester.get_chain()->add_input(input);
930 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
932 // We can set much tighter limits on this than 8-bit Y'CbCr;
933 // even tighter than the default limits.
934 expect_equal(expected_data, out_data, 4 * width, height, 0.002, 0.0003);
937 TEST(YCbCrInputTest, TenBitPlanar) {
939 const int height = 5;
941 // The same data as TenBitInterleaved, but split.
942 uint16_t y[width * height] = {
949 uint16_t cb[width * height] = {
956 uint16_t cr[width * height] = {
963 float expected_data[4 * width * height] = {
970 float out_data[4 * width * height];
972 EffectChainTester tester(nullptr, width, height);
975 format.color_space = COLORSPACE_sRGB;
976 format.gamma_curve = GAMMA_sRGB;
978 YCbCrFormat ycbcr_format;
979 ycbcr_format.luma_coefficients = YCBCR_REC_709;
980 ycbcr_format.full_range = false;
981 ycbcr_format.num_levels = 1024; // 10-bit.
982 ycbcr_format.chroma_subsampling_x = 1;
983 ycbcr_format.chroma_subsampling_y = 1;
984 ycbcr_format.cb_x_position = 0.5f;
985 ycbcr_format.cb_y_position = 0.5f;
986 ycbcr_format.cr_x_position = 0.5f;
987 ycbcr_format.cr_y_position = 0.5f;
989 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_PLANAR, GL_UNSIGNED_SHORT);
990 input->set_pixel_data(0, y);
991 input->set_pixel_data(1, cb);
992 input->set_pixel_data(2, cr);
993 tester.get_chain()->add_input(input);
995 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
997 // We can set much tighter limits on this than 8-bit Y'CbCr;
998 // even tighter than the default limits.
999 expect_equal(expected_data, out_data, 4 * width, height, 0.002, 0.0003);
1002 // Effectively scales down its input linearly by 4x (and repeating it),
1003 // which is not attainable without mipmaps.
1004 class MipmapNeedingEffect : public Effect {
1006 MipmapNeedingEffect() {}
1007 virtual bool needs_mipmaps() const { return true; }
1009 // To be allowed to mess with the sampler state.
1010 virtual bool needs_texture_bounce() const { return true; }
1012 virtual string effect_type_id() const { return "MipmapNeedingEffect"; }
1013 string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
1014 virtual void inform_added(EffectChain *chain) { this->chain = chain; }
1016 void set_gl_state(GLuint glsl_program_num, const string& prefix, unsigned *sampler_num)
1018 Node *self = chain->find_node_for_effect(this);
1019 glActiveTexture(chain->get_input_sampler(self, 0));
1021 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
1023 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
1031 // Basically the same test as EffectChainTest_MipmapGenerationWorks,
1032 // just with the data converted to Y'CbCr (as red only).
1033 TEST(EffectChainTest, MipmapGenerationWorks) {
1034 const unsigned width = 4;
1035 const unsigned height = 16;
1036 float red_data[width * height] = { // In 4x4 blocks.
1037 1.0f, 0.0f, 0.0f, 0.0f,
1038 0.0f, 0.0f, 0.0f, 0.0f,
1039 0.0f, 0.0f, 0.0f, 0.0f,
1040 0.0f, 0.0f, 0.0f, 1.0f,
1042 0.0f, 0.0f, 0.0f, 0.0f,
1043 0.0f, 0.5f, 0.0f, 0.0f,
1044 0.0f, 0.0f, 1.0f, 0.0f,
1045 0.0f, 0.0f, 0.0f, 0.0f,
1047 1.0f, 1.0f, 1.0f, 1.0f,
1048 1.0f, 1.0f, 1.0f, 1.0f,
1049 1.0f, 1.0f, 1.0f, 1.0f,
1050 1.0f, 1.0f, 1.0f, 1.0f,
1052 0.0f, 0.0f, 0.0f, 0.0f,
1053 0.0f, 1.0f, 1.0f, 0.0f,
1054 0.0f, 1.0f, 1.0f, 0.0f,
1055 0.0f, 0.0f, 0.0f, 0.0f,
1057 float expected_data[width * height] = { // Repeated four times each way.
1058 0.125f, 0.125f, 0.125f, 0.125f,
1059 0.09375f, 0.09375f, 0.09375f, 0.09375f,
1060 1.0f, 1.0f, 1.0f, 1.0f,
1061 0.25f, 0.25f, 0.25f, 0.25f,
1063 0.125f, 0.125f, 0.125f, 0.125f,
1064 0.09375f, 0.09375f, 0.09375f, 0.09375f,
1065 1.0f, 1.0f, 1.0f, 1.0f,
1066 0.25f, 0.25f, 0.25f, 0.25f,
1068 0.125f, 0.125f, 0.125f, 0.125f,
1069 0.09375f, 0.09375f, 0.09375f, 0.09375f,
1070 1.0f, 1.0f, 1.0f, 1.0f,
1071 0.25f, 0.25f, 0.25f, 0.25f,
1073 0.125f, 0.125f, 0.125f, 0.125f,
1074 0.09375f, 0.09375f, 0.09375f, 0.09375f,
1075 1.0f, 1.0f, 1.0f, 1.0f,
1076 0.25f, 0.25f, 0.25f, 0.25f,
1078 float expected_data_rgba[width * height * 4];
1079 unsigned char ycbcr_data[width * height * 3];
1081 // Convert to Y'CbCr.
1082 YCbCrFormat ycbcr_format;
1083 ycbcr_format.luma_coefficients = YCBCR_REC_709;
1084 ycbcr_format.full_range = false;
1085 ycbcr_format.num_levels = 256;
1086 ycbcr_format.chroma_subsampling_x = 1;
1087 ycbcr_format.chroma_subsampling_y = 1;
1088 ycbcr_format.cb_x_position = 0.5f;
1089 ycbcr_format.cb_y_position = 0.5f;
1090 ycbcr_format.cr_x_position = 0.5f;
1091 ycbcr_format.cr_y_position = 0.5f;
1094 Eigen::Matrix3d ycbcr_to_rgb;
1095 compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
1097 Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse();
1098 for (unsigned i = 0; i < 64; ++i) {
1099 Eigen::Vector3d rgb(red_data[i], 0.0, 0.0);
1100 Eigen::Vector3d ycbcr = rgb_to_ycbcr * rgb;
1101 ycbcr(0) += offset[0];
1102 ycbcr(1) += offset[1];
1103 ycbcr(2) += offset[2];
1104 ycbcr_data[i * 3 + 0] = lrintf(ycbcr(0) * 255.0);
1105 ycbcr_data[i * 3 + 1] = lrintf(ycbcr(1) * 255.0);
1106 ycbcr_data[i * 3 + 2] = lrintf(ycbcr(2) * 255.0);
1109 // Expand expected_data to RGBA.
1110 for (unsigned i = 0; i < 64; ++i) {
1111 expected_data_rgba[i * 4 + 0] = expected_data[i];
1112 expected_data_rgba[i * 4 + 1] = 0.0f;
1113 expected_data_rgba[i * 4 + 2] = 0.0f;
1114 expected_data_rgba[i * 4 + 3] = 1.0f;
1118 format.color_space = COLORSPACE_sRGB;
1119 format.gamma_curve = GAMMA_sRGB;
1121 float out_data[width * height * 4];
1122 EffectChainTester tester(nullptr, width, height);
1123 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_INTERLEAVED);
1124 input->set_pixel_data(0, ycbcr_data);
1125 tester.get_chain()->add_input(input);
1126 tester.get_chain()->add_effect(new MipmapNeedingEffect());
1127 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
1129 // The usual pretty loose limits.
1130 expect_equal(expected_data_rgba, out_data, width * 4, height, 0.025, 0.002);
1133 } // namespace movit