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"
18 TEST(YCbCrInputTest, Simple444) {
22 // Pure-color test inputs, calculated with the formulas in Rec. 601
24 unsigned char y[width * height] = {
27 unsigned char cb[width * height] = {
28 128, 128, 90, 54, 240,
30 unsigned char cr[width * height] = {
31 128, 128, 240, 34, 110,
33 float expected_data[4 * width * height] = {
40 float out_data[4 * width * height];
42 EffectChainTester tester(NULL, width, height);
45 format.color_space = COLORSPACE_sRGB;
46 format.gamma_curve = GAMMA_sRGB;
48 YCbCrFormat ycbcr_format;
49 ycbcr_format.luma_coefficients = YCBCR_REC_601;
50 ycbcr_format.full_range = false;
51 ycbcr_format.num_levels = 256;
52 ycbcr_format.chroma_subsampling_x = 1;
53 ycbcr_format.chroma_subsampling_y = 1;
54 ycbcr_format.cb_x_position = 0.5f;
55 ycbcr_format.cb_y_position = 0.5f;
56 ycbcr_format.cr_x_position = 0.5f;
57 ycbcr_format.cr_y_position = 0.5f;
59 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
60 input->set_pixel_data(0, y);
61 input->set_pixel_data(1, cb);
62 input->set_pixel_data(2, cr);
63 tester.get_chain()->add_input(input);
65 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
67 // Y'CbCr isn't 100% accurate (the input values are rounded),
68 // so we need some leeway.
69 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
72 TEST(YCbCrInputTest, Interleaved444) {
76 // Same data as Simple444, just rearranged.
77 unsigned char data[width * height * 3] = {
84 float expected_data[4 * width * height] = {
91 float out_data[4 * width * height];
93 EffectChainTester tester(NULL, width, height);
96 format.color_space = COLORSPACE_sRGB;
97 format.gamma_curve = GAMMA_sRGB;
99 YCbCrFormat ycbcr_format;
100 ycbcr_format.luma_coefficients = YCBCR_REC_601;
101 ycbcr_format.full_range = false;
102 ycbcr_format.num_levels = 256;
103 ycbcr_format.chroma_subsampling_x = 1;
104 ycbcr_format.chroma_subsampling_y = 1;
105 ycbcr_format.cb_x_position = 0.5f;
106 ycbcr_format.cb_y_position = 0.5f;
107 ycbcr_format.cr_x_position = 0.5f;
108 ycbcr_format.cr_y_position = 0.5f;
110 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_INTERLEAVED);
111 input->set_pixel_data(0, data);
112 tester.get_chain()->add_input(input);
114 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
116 // Y'CbCr isn't 100% accurate (the input values are rounded),
117 // so we need some leeway.
118 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
121 TEST(YCbCrInputTest, FullRangeRec601) {
123 const int height = 5;
125 // Pure-color test inputs, calculated with the formulas in Rec. 601
126 // section 2.5.4 but without the scaling factors applied
127 // (so both R, G, B, Y, Cb and R vary from 0 to 255).
128 unsigned char y[width * height] = {
131 unsigned char cb[width * height] = {
132 128, 128, 85, 44, 255,
134 unsigned char cr[width * height] = {
135 128, 128, 255, 21, 107,
137 float expected_data[4 * width * height] = {
144 float out_data[4 * width * height];
146 EffectChainTester tester(NULL, width, height);
149 format.color_space = COLORSPACE_sRGB;
150 format.gamma_curve = GAMMA_sRGB;
152 YCbCrFormat ycbcr_format;
153 ycbcr_format.luma_coefficients = YCBCR_REC_601;
154 ycbcr_format.full_range = true;
155 ycbcr_format.num_levels = 256;
156 ycbcr_format.chroma_subsampling_x = 1;
157 ycbcr_format.chroma_subsampling_y = 1;
158 ycbcr_format.cb_x_position = 0.5f;
159 ycbcr_format.cb_y_position = 0.5f;
160 ycbcr_format.cr_x_position = 0.5f;
161 ycbcr_format.cr_y_position = 0.5f;
163 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
164 input->set_pixel_data(0, y);
165 input->set_pixel_data(1, cb);
166 input->set_pixel_data(2, cr);
167 tester.get_chain()->add_input(input);
169 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
171 // Y'CbCr isn't 100% accurate (the input values are rounded),
172 // so we need some leeway.
173 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
176 TEST(YCbCrInputTest, Rec709) {
178 const int height = 5;
180 // Pure-color test inputs, calculated with the formulas in Rec. 709
181 // page 19, items 3.4 and 3.5.
182 unsigned char y[width * height] = {
183 16, 235, 63, 173, 32,
185 unsigned char cb[width * height] = {
186 128, 128, 102, 42, 240,
188 unsigned char cr[width * height] = {
189 128, 128, 240, 26, 118,
191 float expected_data[4 * width * height] = {
198 float out_data[4 * width * height];
200 EffectChainTester tester(NULL, width, height);
203 format.color_space = COLORSPACE_sRGB;
204 format.gamma_curve = GAMMA_sRGB;
206 YCbCrFormat ycbcr_format;
207 ycbcr_format.luma_coefficients = YCBCR_REC_709;
208 ycbcr_format.full_range = false;
209 ycbcr_format.num_levels = 256;
210 ycbcr_format.chroma_subsampling_x = 1;
211 ycbcr_format.chroma_subsampling_y = 1;
212 ycbcr_format.cb_x_position = 0.5f;
213 ycbcr_format.cb_y_position = 0.5f;
214 ycbcr_format.cr_x_position = 0.5f;
215 ycbcr_format.cr_y_position = 0.5f;
217 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
218 input->set_pixel_data(0, y);
219 input->set_pixel_data(1, cb);
220 input->set_pixel_data(2, cr);
221 tester.get_chain()->add_input(input);
223 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
225 // Y'CbCr isn't 100% accurate (the input values are rounded),
226 // so we need some leeway.
227 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
230 TEST(YCbCrInputTest, Rec2020) {
232 const int height = 5;
234 // Pure-color test inputs, calculated with the formulas in Rec. 2020
235 // page 4, tables 4 and 5 (for conventional non-constant luminance).
236 // Note that we still use 8-bit inputs, even though Rec. 2020 is only
237 // defined for 10- and 12-bit.
238 unsigned char y[width * height] = {
239 16, 235, 74, 164, 29,
241 unsigned char cb[width * height] = {
242 128, 128, 97, 47, 240,
244 unsigned char cr[width * height] = {
245 128, 128, 240, 25, 119,
247 float expected_data[4 * width * height] = {
254 float out_data[4 * width * height];
256 EffectChainTester tester(NULL, width, height);
259 format.color_space = COLORSPACE_sRGB;
260 format.gamma_curve = GAMMA_sRGB;
262 YCbCrFormat ycbcr_format;
263 ycbcr_format.luma_coefficients = YCBCR_REC_2020;
264 ycbcr_format.full_range = false;
265 ycbcr_format.num_levels = 256;
266 ycbcr_format.chroma_subsampling_x = 1;
267 ycbcr_format.chroma_subsampling_y = 1;
268 ycbcr_format.cb_x_position = 0.5f;
269 ycbcr_format.cb_y_position = 0.5f;
270 ycbcr_format.cr_x_position = 0.5f;
271 ycbcr_format.cr_y_position = 0.5f;
273 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
274 input->set_pixel_data(0, y);
275 input->set_pixel_data(1, cb);
276 input->set_pixel_data(2, cr);
277 tester.get_chain()->add_input(input);
279 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
281 // Y'CbCr isn't 100% accurate (the input values are rounded),
282 // so we need some leeway.
283 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
286 TEST(YCbCrInputTest, Subsampling420) {
288 const int height = 4;
290 unsigned char y[width * height] = {
296 unsigned char cb[(width/2) * (height/2)] = {
300 unsigned char cr[(width/2) * (height/2)] = {
305 // Note: This is only the blue channel. The chroma samples (with associated
306 // values for blue) are marked off in comments.
307 float expected_data[width * height] = {
308 0.000, 0.125, 0.375, 0.500,
310 0.125, 0.250, 0.500, 0.625,
312 0.375, 0.500, 0.750, 0.875,
314 0.500, 0.625, 0.875, 1.000,
316 float out_data[width * height];
318 EffectChainTester tester(NULL, width, height);
321 format.color_space = COLORSPACE_sRGB;
322 format.gamma_curve = GAMMA_sRGB;
324 YCbCrFormat ycbcr_format;
325 ycbcr_format.luma_coefficients = YCBCR_REC_601;
326 ycbcr_format.full_range = false;
327 ycbcr_format.num_levels = 256;
328 ycbcr_format.chroma_subsampling_x = 2;
329 ycbcr_format.chroma_subsampling_y = 2;
330 ycbcr_format.cb_x_position = 0.5f;
331 ycbcr_format.cb_y_position = 0.5f;
332 ycbcr_format.cr_x_position = 0.5f;
333 ycbcr_format.cr_y_position = 0.5f;
335 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
336 input->set_pixel_data(0, y);
337 input->set_pixel_data(1, cb);
338 input->set_pixel_data(2, cr);
339 tester.get_chain()->add_input(input);
341 tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
343 // Y'CbCr isn't 100% accurate (the input values are rounded),
344 // so we need some leeway.
345 expect_equal(expected_data, out_data, width, height, 0.01, 0.001);
348 TEST(YCbCrInputTest, Subsampling420WithNonCenteredSamples) {
350 const int height = 4;
352 unsigned char y[width * height] = {
358 unsigned char cb[(width/2) * (height/2)] = {
362 unsigned char cr[(width/2) * (height/2)] = {
367 // Note: This is only the blue channel. The chroma samples (with associated
368 // values for blue) are marked off in comments.
369 float expected_data[width * height] = {
370 0.000, 0.250, 0.500, 0.500,
372 0.125, 0.375, 0.625, 0.625,
374 0.375, 0.625, 0.875, 0.875,
376 0.500, 0.750, 1.000, 1.000,
378 float out_data[width * height];
380 EffectChainTester tester(NULL, width, height);
383 format.color_space = COLORSPACE_sRGB;
384 format.gamma_curve = GAMMA_sRGB;
386 YCbCrFormat ycbcr_format;
387 ycbcr_format.luma_coefficients = YCBCR_REC_601;
388 ycbcr_format.full_range = false;
389 ycbcr_format.num_levels = 256;
390 ycbcr_format.chroma_subsampling_x = 2;
391 ycbcr_format.chroma_subsampling_y = 2;
392 ycbcr_format.cb_x_position = 0.0f;
393 ycbcr_format.cb_y_position = 0.5f;
394 ycbcr_format.cr_x_position = 0.0f;
395 ycbcr_format.cr_y_position = 0.5f;
397 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
398 input->set_pixel_data(0, y);
399 input->set_pixel_data(1, cb);
400 input->set_pixel_data(2, cr);
401 tester.get_chain()->add_input(input);
403 tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
405 // Y'CbCr isn't 100% accurate (the input values are rounded),
406 // so we need some leeway.
407 expect_equal(expected_data, out_data, width, height, 0.01, 0.0012);
410 // Yes, some 4:2:2 formats actually have this craziness.
411 TEST(YCbCrInputTest, DifferentCbAndCrPositioning) {
413 const int height = 4;
415 unsigned char y[width * height] = {
421 unsigned char cb[(width/2) * height] = {
427 unsigned char cr[(width/2) * height] = {
434 // Chroma samples in this csae are always co-sited with a luma sample;
435 // their associated color values and position are marked off in comments.
436 float expected_data_blue[width * height] = {
437 0.000 /* 0.0 */, 0.250, 0.500 /* 0.5 */, 0.500,
438 0.500 /* 0.5 */, 0.750, 1.000 /* 1.0 */, 1.000,
439 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */, 0.500,
440 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */, 0.500,
442 float expected_data_red[width * height] = {
443 0.000, 0.000 /* 0.0 */, 0.250, 0.500 /* 0.5 */,
444 0.500, 0.500 /* 0.5 */, 0.750, 1.000 /* 1.0 */,
445 0.500, 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */,
446 0.500, 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */,
448 float out_data[width * height];
450 EffectChainTester tester(NULL, width, height);
453 format.color_space = COLORSPACE_sRGB;
454 format.gamma_curve = GAMMA_sRGB;
456 YCbCrFormat ycbcr_format;
457 ycbcr_format.luma_coefficients = YCBCR_REC_601;
458 ycbcr_format.full_range = false;
459 ycbcr_format.num_levels = 256;
460 ycbcr_format.chroma_subsampling_x = 2;
461 ycbcr_format.chroma_subsampling_y = 1;
462 ycbcr_format.cb_x_position = 0.0f;
463 ycbcr_format.cb_y_position = 0.5f;
464 ycbcr_format.cr_x_position = 1.0f;
465 ycbcr_format.cr_y_position = 0.5f;
467 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
468 input->set_pixel_data(0, y);
469 input->set_pixel_data(1, cb);
470 input->set_pixel_data(2, cr);
471 tester.get_chain()->add_input(input);
473 // Y'CbCr isn't 100% accurate (the input values are rounded),
474 // so we need some leeway.
475 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
476 expect_equal(expected_data_red, out_data, width, height, 0.02, 0.002);
478 tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
479 expect_equal(expected_data_blue, out_data, width, height, 0.01, 0.001);
482 TEST(YCbCrInputTest, PBO) {
484 const int height = 5;
486 // Pure-color test inputs, calculated with the formulas in Rec. 601
488 unsigned char data[width * height * 3] = {
489 16, 235, 81, 145, 41,
490 128, 128, 90, 54, 240,
491 128, 128, 240, 34, 110,
493 float expected_data[4 * width * height] = {
500 float out_data[4 * width * height];
503 glGenBuffers(1, &pbo);
504 glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo);
505 glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, width * height * 3, data, GL_STREAM_DRAW);
506 glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
508 EffectChainTester tester(NULL, width, height);
511 format.color_space = COLORSPACE_sRGB;
512 format.gamma_curve = GAMMA_sRGB;
514 YCbCrFormat ycbcr_format;
515 ycbcr_format.luma_coefficients = YCBCR_REC_601;
516 ycbcr_format.full_range = false;
517 ycbcr_format.num_levels = 256;
518 ycbcr_format.chroma_subsampling_x = 1;
519 ycbcr_format.chroma_subsampling_y = 1;
520 ycbcr_format.cb_x_position = 0.5f;
521 ycbcr_format.cb_y_position = 0.5f;
522 ycbcr_format.cr_x_position = 0.5f;
523 ycbcr_format.cr_y_position = 0.5f;
525 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
526 input->set_pixel_data(0, (unsigned char *)BUFFER_OFFSET(0), pbo);
527 input->set_pixel_data(1, (unsigned char *)BUFFER_OFFSET(width * height), pbo);
528 input->set_pixel_data(2, (unsigned char *)BUFFER_OFFSET(width * height * 2), pbo);
529 tester.get_chain()->add_input(input);
531 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
533 // Y'CbCr isn't 100% accurate (the input values are rounded),
534 // so we need some leeway.
535 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
537 glDeleteBuffers(1, &pbo);
540 TEST(YCbCrInputTest, CombinedCbAndCr) {
542 const int height = 5;
544 // Pure-color test inputs, calculated with the formulas in Rec. 601
546 unsigned char y[width * height] = {
547 16, 235, 81, 145, 41,
549 unsigned char cb_cr[width * height * 2] = {
556 float expected_data[4 * width * height] = {
563 float out_data[4 * width * height];
565 EffectChainTester tester(NULL, width, height);
568 format.color_space = COLORSPACE_sRGB;
569 format.gamma_curve = GAMMA_sRGB;
571 YCbCrFormat ycbcr_format;
572 ycbcr_format.luma_coefficients = YCBCR_REC_601;
573 ycbcr_format.full_range = false;
574 ycbcr_format.num_levels = 256;
575 ycbcr_format.chroma_subsampling_x = 1;
576 ycbcr_format.chroma_subsampling_y = 1;
577 ycbcr_format.cb_x_position = 0.5f;
578 ycbcr_format.cb_y_position = 0.5f;
579 ycbcr_format.cr_x_position = 0.5f;
580 ycbcr_format.cr_y_position = 0.5f;
582 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_SPLIT_Y_AND_CBCR);
583 input->set_pixel_data(0, y);
584 input->set_pixel_data(1, cb_cr);
585 tester.get_chain()->add_input(input);
587 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
589 // Y'CbCr isn't 100% accurate (the input values are rounded),
590 // so we need some leeway.
591 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
594 TEST(YCbCrInputTest, ExternalTexture) {
596 const int height = 5;
598 // Pure-color test inputs, calculated with the formulas in Rec. 601
600 unsigned char y[width * height] = {
601 16, 235, 81, 145, 41,
603 unsigned char cb[width * height] = {
604 128, 128, 90, 54, 240,
606 unsigned char cr[width * height] = {
607 128, 128, 240, 34, 110,
609 float expected_data[4 * width * height] = {
616 float out_data[4 * width * height];
618 EffectChainTester tester(NULL, width, height);
621 format.color_space = COLORSPACE_sRGB;
622 format.gamma_curve = GAMMA_sRGB;
624 YCbCrFormat ycbcr_format;
625 ycbcr_format.luma_coefficients = YCBCR_REC_601;
626 ycbcr_format.full_range = false;
627 ycbcr_format.num_levels = 256;
628 ycbcr_format.chroma_subsampling_x = 1;
629 ycbcr_format.chroma_subsampling_y = 1;
630 ycbcr_format.cb_x_position = 0.5f;
631 ycbcr_format.cb_y_position = 0.5f;
632 ycbcr_format.cr_x_position = 0.5f;
633 ycbcr_format.cr_y_position = 0.5f;
635 // Make a texture for the Cb data; keep the others as regular uploads.
637 GLuint cb_tex = pool.create_2d_texture(GL_R8, width, height);
639 glBindTexture(GL_TEXTURE_2D, cb_tex);
641 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
643 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
645 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, cb);
647 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
649 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
652 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
653 input->set_pixel_data(0, y);
654 input->set_texture_num(1, cb_tex);
655 input->set_pixel_data(2, cr);
656 tester.get_chain()->add_input(input);
658 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
660 pool.release_2d_texture(cb_tex);
662 // Y'CbCr isn't 100% accurate (the input values are rounded),
663 // so we need some leeway.
664 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
667 TEST(YCbCrTest, WikipediaRec601ForwardMatrix) {
668 YCbCrFormat ycbcr_format;
669 ycbcr_format.luma_coefficients = YCBCR_REC_601;
670 ycbcr_format.full_range = false;
671 ycbcr_format.num_levels = 256;
674 Eigen::Matrix3d ycbcr_to_rgb;
675 compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
677 Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse() * 255.0;
679 // Values from https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion.
680 EXPECT_NEAR( 65.481, rgb_to_ycbcr(0,0), 1e-3);
681 EXPECT_NEAR( 128.553, rgb_to_ycbcr(0,1), 1e-3);
682 EXPECT_NEAR( 24.966, rgb_to_ycbcr(0,2), 1e-3);
684 EXPECT_NEAR( -37.797, rgb_to_ycbcr(1,0), 1e-3);
685 EXPECT_NEAR( -74.203, rgb_to_ycbcr(1,1), 1e-3);
686 EXPECT_NEAR( 112.000, rgb_to_ycbcr(1,2), 1e-3);
688 EXPECT_NEAR( 112.000, rgb_to_ycbcr(2,0), 1e-3);
689 EXPECT_NEAR( -93.786, rgb_to_ycbcr(2,1), 1e-3);
690 EXPECT_NEAR( -18.214, rgb_to_ycbcr(2,2), 1e-3);
692 EXPECT_NEAR( 16.0, offset[0] * 255.0, 1e-3);
693 EXPECT_NEAR(128.0, offset[1] * 255.0, 1e-3);
694 EXPECT_NEAR(128.0, offset[2] * 255.0, 1e-3);
697 TEST(YCbCrTest, WikipediaJPEGMatrices) {
698 YCbCrFormat ycbcr_format;
699 ycbcr_format.luma_coefficients = YCBCR_REC_601;
700 ycbcr_format.full_range = true;
701 ycbcr_format.num_levels = 256;
704 Eigen::Matrix3d ycbcr_to_rgb;
705 compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
707 // Values from https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion.
708 EXPECT_NEAR( 1.00000, ycbcr_to_rgb(0,0), 1e-5);
709 EXPECT_NEAR( 0.00000, ycbcr_to_rgb(0,1), 1e-5);
710 EXPECT_NEAR( 1.40200, ycbcr_to_rgb(0,2), 1e-5);
712 EXPECT_NEAR( 1.00000, ycbcr_to_rgb(1,0), 1e-5);
713 EXPECT_NEAR(-0.34414, ycbcr_to_rgb(1,1), 1e-5);
714 EXPECT_NEAR(-0.71414, ycbcr_to_rgb(1,2), 1e-5);
716 EXPECT_NEAR( 1.00000, ycbcr_to_rgb(2,0), 1e-5);
717 EXPECT_NEAR( 1.77200, ycbcr_to_rgb(2,1), 1e-5);
718 EXPECT_NEAR( 0.00000, ycbcr_to_rgb(2,2), 1e-5);
720 Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse();
723 EXPECT_NEAR( 0.299000, rgb_to_ycbcr(0,0), 1e-6);
724 EXPECT_NEAR( 0.587000, rgb_to_ycbcr(0,1), 1e-6);
725 EXPECT_NEAR( 0.114000, rgb_to_ycbcr(0,2), 1e-6);
727 EXPECT_NEAR(-0.168736, rgb_to_ycbcr(1,0), 1e-6);
728 EXPECT_NEAR(-0.331264, rgb_to_ycbcr(1,1), 1e-6);
729 EXPECT_NEAR( 0.500000, rgb_to_ycbcr(1,2), 1e-6);
731 EXPECT_NEAR( 0.500000, rgb_to_ycbcr(2,0), 1e-6);
732 EXPECT_NEAR(-0.418688, rgb_to_ycbcr(2,1), 1e-6);
733 EXPECT_NEAR(-0.081312, rgb_to_ycbcr(2,2), 1e-6);
735 EXPECT_NEAR( 0.0, offset[0] * 255.0, 1e-3);
736 EXPECT_NEAR(128.0, offset[1] * 255.0, 1e-3);
737 EXPECT_NEAR(128.0, offset[2] * 255.0, 1e-3);
740 TEST(YCbCrTest, BlackmagicForwardMatrix) {
741 YCbCrFormat ycbcr_format;
742 ycbcr_format.luma_coefficients = YCBCR_REC_709;
743 ycbcr_format.full_range = false;
744 ycbcr_format.num_levels = 256;
747 Eigen::Matrix3d ycbcr_to_rgb;
748 compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
750 Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse();
752 // Values from DeckLink SDK documentation.
753 EXPECT_NEAR( 0.183, rgb_to_ycbcr(0,0), 1e-3);
754 EXPECT_NEAR( 0.614, rgb_to_ycbcr(0,1), 1e-3);
755 EXPECT_NEAR( 0.062, rgb_to_ycbcr(0,2), 1e-3);
757 EXPECT_NEAR(-0.101, rgb_to_ycbcr(1,0), 1e-3);
758 EXPECT_NEAR(-0.338, rgb_to_ycbcr(1,1), 1e-3);
759 EXPECT_NEAR( 0.439, rgb_to_ycbcr(1,2), 1e-3);
761 EXPECT_NEAR( 0.439, rgb_to_ycbcr(2,0), 1e-3);
762 EXPECT_NEAR(-0.399, rgb_to_ycbcr(2,1), 1e-3);
763 EXPECT_NEAR(-0.040, rgb_to_ycbcr(2,2), 1e-3);
765 EXPECT_NEAR( 16.0, offset[0] * 255.0, 1e-3);
766 EXPECT_NEAR(128.0, offset[1] * 255.0, 1e-3);
767 EXPECT_NEAR(128.0, offset[2] * 255.0, 1e-3);
770 TEST(YCbCrInputTest, NoData) {
772 const int height = 5;
774 float out_data[4 * width * height];
776 EffectChainTester tester(NULL, width, height);
779 format.color_space = COLORSPACE_sRGB;
780 format.gamma_curve = GAMMA_sRGB;
782 YCbCrFormat ycbcr_format;
783 ycbcr_format.luma_coefficients = YCBCR_REC_601;
784 ycbcr_format.full_range = false;
785 ycbcr_format.num_levels = 256;
786 ycbcr_format.chroma_subsampling_x = 1;
787 ycbcr_format.chroma_subsampling_y = 1;
788 ycbcr_format.cb_x_position = 0.5f;
789 ycbcr_format.cb_y_position = 0.5f;
790 ycbcr_format.cr_x_position = 0.5f;
791 ycbcr_format.cr_y_position = 0.5f;
793 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
794 tester.get_chain()->add_input(input);
796 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
798 // Don't care what the output was, just that it does not crash.
801 TEST(YCbCrInputTest, TenBitInterleaved) {
803 const int height = 5;
805 // Pure-color inputs, calculated using formulas 3.2, 3.3 and 3.4 from
806 // Rec. 709. (Except the first two, which are obvious given the 64–940
807 // range of luminance.)
808 unsigned expanded_data[width * height * 3] = {
815 float expected_data[4 * width * height] = {
822 float out_data[4 * width * height];
824 // Pack 32:32:32 to 10:10:10:2.
825 uint32_t data[width * height];
826 for (unsigned i = 0; i < width * height; ++i) {
828 expanded_data[i * 3 + 0] |
829 (expanded_data[i * 3 + 1] << 10) |
830 (expanded_data[i * 3 + 2] << 20);
833 EffectChainTester tester(NULL, width, height);
836 format.color_space = COLORSPACE_sRGB;
837 format.gamma_curve = GAMMA_sRGB;
839 YCbCrFormat ycbcr_format;
840 ycbcr_format.luma_coefficients = YCBCR_REC_709;
841 ycbcr_format.full_range = false;
842 ycbcr_format.num_levels = 1024; // 10-bit.
843 ycbcr_format.chroma_subsampling_x = 1;
844 ycbcr_format.chroma_subsampling_y = 1;
845 ycbcr_format.cb_x_position = 0.5f;
846 ycbcr_format.cb_y_position = 0.5f;
847 ycbcr_format.cr_x_position = 0.5f;
848 ycbcr_format.cr_y_position = 0.5f;
850 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_INTERLEAVED, GL_UNSIGNED_INT_2_10_10_10_REV);
851 input->set_pixel_data(0, data);
852 tester.get_chain()->add_input(input);
854 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
856 // We can set much tighter limits on this than 8-bit Y'CbCr;
857 // even tighter than the default limits.
858 expect_equal(expected_data, out_data, 4 * width, height, 0.002, 0.0003);