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(NULL, 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(NULL, 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(NULL, 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(NULL, 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(NULL, 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 TEST(YCbCrInputTest, Subsampling420) {
290 const int height = 4;
292 unsigned char y[width * height] = {
298 unsigned char cb[(width/2) * (height/2)] = {
302 unsigned char cr[(width/2) * (height/2)] = {
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,
312 0.125, 0.250, 0.500, 0.625,
314 0.375, 0.500, 0.750, 0.875,
316 0.500, 0.625, 0.875, 1.000,
318 float out_data[width * height];
320 EffectChainTester tester(NULL, width, height);
323 format.color_space = COLORSPACE_sRGB;
324 format.gamma_curve = GAMMA_sRGB;
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;
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);
343 tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
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);
350 TEST(YCbCrInputTest, Subsampling420WithNonCenteredSamples) {
352 const int height = 4;
354 unsigned char y[width * height] = {
360 unsigned char cb[(width/2) * (height/2)] = {
364 unsigned char cr[(width/2) * (height/2)] = {
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,
374 0.125, 0.375, 0.625, 0.625,
376 0.375, 0.625, 0.875, 0.875,
378 0.500, 0.750, 1.000, 1.000,
380 float out_data[width * height];
382 EffectChainTester tester(NULL, width, height);
385 format.color_space = COLORSPACE_sRGB;
386 format.gamma_curve = GAMMA_sRGB;
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;
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);
405 tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
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);
412 // Yes, some 4:2:2 formats actually have this craziness.
413 TEST(YCbCrInputTest, DifferentCbAndCrPositioning) {
415 const int height = 4;
417 unsigned char y[width * height] = {
423 unsigned char cb[(width/2) * height] = {
429 unsigned char cr[(width/2) * height] = {
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,
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 */,
450 float out_data[width * height];
452 EffectChainTester tester(NULL, width, height);
455 format.color_space = COLORSPACE_sRGB;
456 format.gamma_curve = GAMMA_sRGB;
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;
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);
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);
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);
484 TEST(YCbCrInputTest, PBO) {
486 const int height = 5;
488 // Pure-color test inputs, calculated with the formulas in Rec. 601
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,
495 float expected_data[4 * width * height] = {
502 float out_data[4 * width * height];
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);
510 EffectChainTester tester(NULL, width, height);
513 format.color_space = COLORSPACE_sRGB;
514 format.gamma_curve = GAMMA_sRGB;
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;
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);
533 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
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);
539 glDeleteBuffers(1, &pbo);
542 TEST(YCbCrInputTest, CombinedCbAndCr) {
544 const int height = 5;
546 // Pure-color test inputs, calculated with the formulas in Rec. 601
548 unsigned char y[width * height] = {
549 16, 235, 81, 145, 41,
551 unsigned char cb_cr[width * height * 2] = {
558 float expected_data[4 * width * height] = {
565 float out_data[4 * width * height];
567 EffectChainTester tester(NULL, width, height);
570 format.color_space = COLORSPACE_sRGB;
571 format.gamma_curve = GAMMA_sRGB;
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;
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);
589 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
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);
596 TEST(YCbCrInputTest, ExternalTexture) {
598 const int height = 5;
600 // Pure-color test inputs, calculated with the formulas in Rec. 601
602 unsigned char y[width * height] = {
603 16, 235, 81, 145, 41,
605 unsigned char cb[width * height] = {
606 128, 128, 90, 54, 240,
608 unsigned char cr[width * height] = {
609 128, 128, 240, 34, 110,
611 float expected_data[4 * width * height] = {
618 float out_data[4 * width * height];
620 EffectChainTester tester(NULL, width, height);
623 format.color_space = COLORSPACE_sRGB;
624 format.gamma_curve = GAMMA_sRGB;
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;
637 // Make a texture for the Cb data; keep the others as regular uploads.
639 GLuint cb_tex = pool.create_2d_texture(GL_R8, width, height);
641 glBindTexture(GL_TEXTURE_2D, cb_tex);
643 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
645 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
647 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, cb);
649 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
651 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
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);
660 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
662 pool.release_2d_texture(cb_tex);
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);
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;
676 Eigen::Matrix3d ycbcr_to_rgb;
677 compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
679 Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse() * 255.0;
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);
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);
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);
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);
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;
706 Eigen::Matrix3d ycbcr_to_rgb;
707 compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
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);
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);
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);
722 Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse();
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);
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);
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);
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);
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;
749 Eigen::Matrix3d ycbcr_to_rgb;
750 compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
752 Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse();
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);
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);
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);
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);
772 TEST(YCbCrInputTest, NoData) {
774 const int height = 5;
776 float out_data[4 * width * height];
778 EffectChainTester tester(NULL, width, height);
781 format.color_space = COLORSPACE_sRGB;
782 format.gamma_curve = GAMMA_sRGB;
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;
795 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
796 tester.get_chain()->add_input(input);
798 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
800 // Don't care what the output was, just that it does not crash.
803 TEST(YCbCrInputTest, TenBitInterleaved) {
805 const int height = 5;
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] = {
817 float expected_data[4 * width * height] = {
824 float out_data[4 * width * height];
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) {
830 expanded_data[i * 3 + 0] |
831 (expanded_data[i * 3 + 1] << 10) |
832 (expanded_data[i * 3 + 2] << 20);
835 EffectChainTester tester(NULL, width, height);
838 format.color_space = COLORSPACE_sRGB;
839 format.gamma_curve = GAMMA_sRGB;
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;
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);
856 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
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);
863 TEST(YCbCrInputTest, TenBitPlanar) {
865 const int height = 5;
867 // The same data as TenBitInterleaved, but split.
868 uint16_t y[width * height] = {
875 uint16_t cb[width * height] = {
882 uint16_t cr[width * height] = {
889 float expected_data[4 * width * height] = {
896 float out_data[4 * width * height];
898 EffectChainTester tester(NULL, width, height);
901 format.color_space = COLORSPACE_sRGB;
902 format.gamma_curve = GAMMA_sRGB;
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;
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);
921 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
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);
928 // Effectively scales down its input linearly by 4x (and repeating it),
929 // which is not attainable without mipmaps.
930 class MipmapNeedingEffect : public Effect {
932 MipmapNeedingEffect() {}
933 virtual bool needs_mipmaps() const { return true; }
935 // To be allowed to mess with the sampler state.
936 virtual bool needs_texture_bounce() const { return true; }
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; }
942 void set_gl_state(GLuint glsl_program_num, const string& prefix, unsigned *sampler_num)
944 Node *self = chain->find_node_for_effect(this);
945 glActiveTexture(chain->get_input_sampler(self, 0));
947 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
949 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
957 // Basically the same test as EffectChainTest_MipmapGenerationWorks,
958 // just with the data converted to Y'CbCr (as red only).
959 TEST(EffectChainTest, MipmapGenerationWorks) {
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,
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,
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,
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,
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,
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,
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,
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,
1004 float expected_data_rgba[width * height * 4];
1005 unsigned char ycbcr_data[width * height * 3];
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;
1020 Eigen::Matrix3d ycbcr_to_rgb;
1021 compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
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);
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;
1044 format.color_space = COLORSPACE_sRGB;
1045 format.gamma_curve = GAMMA_sRGB;
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);
1055 // The usual pretty loose limits.
1056 expect_equal(expected_data_rgba, out_data, width * 4, height, 0.025, 0.002);
1059 } // namespace movit