1 // Unit tests for YCbCrInput.
6 #include "effect_chain.h"
7 #include "gtest/gtest.h"
10 #include "resource_pool.h"
11 #include "ycbcr_input.h"
15 TEST(YCbCrInputTest, Simple444) {
19 // Pure-color test inputs, calculated with the formulas in Rec. 601
21 unsigned char y[width * height] = {
24 unsigned char cb[width * height] = {
25 128, 128, 90, 54, 240,
27 unsigned char cr[width * height] = {
28 128, 128, 240, 34, 110,
30 float expected_data[4 * width * height] = {
37 float out_data[4 * width * height];
39 EffectChainTester tester(NULL, width, height);
42 format.color_space = COLORSPACE_sRGB;
43 format.gamma_curve = GAMMA_sRGB;
45 YCbCrFormat ycbcr_format;
46 ycbcr_format.luma_coefficients = YCBCR_REC_601;
47 ycbcr_format.full_range = false;
48 ycbcr_format.num_levels = 256;
49 ycbcr_format.chroma_subsampling_x = 1;
50 ycbcr_format.chroma_subsampling_y = 1;
51 ycbcr_format.cb_x_position = 0.5f;
52 ycbcr_format.cb_y_position = 0.5f;
53 ycbcr_format.cr_x_position = 0.5f;
54 ycbcr_format.cr_y_position = 0.5f;
56 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
57 input->set_pixel_data(0, y);
58 input->set_pixel_data(1, cb);
59 input->set_pixel_data(2, cr);
60 tester.get_chain()->add_input(input);
62 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
64 // Y'CbCr isn't 100% accurate (the input values are rounded),
65 // so we need some leeway.
66 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
69 TEST(YCbCrInputTest, FullRangeRec601) {
73 // Pure-color test inputs, calculated with the formulas in Rec. 601
74 // section 2.5.4 but without the scaling factors applied
75 // (so both R, G, B, Y, Cb and R vary from 0 to 255).
76 unsigned char y[width * height] = {
79 unsigned char cb[width * height] = {
80 128, 128, 85, 44, 255,
82 unsigned char cr[width * height] = {
83 128, 128, 255, 21, 107,
85 float expected_data[4 * width * height] = {
92 float out_data[4 * width * height];
94 EffectChainTester tester(NULL, width, height);
97 format.color_space = COLORSPACE_sRGB;
98 format.gamma_curve = GAMMA_sRGB;
100 YCbCrFormat ycbcr_format;
101 ycbcr_format.luma_coefficients = YCBCR_REC_601;
102 ycbcr_format.full_range = true;
103 ycbcr_format.num_levels = 256;
104 ycbcr_format.chroma_subsampling_x = 1;
105 ycbcr_format.chroma_subsampling_y = 1;
106 ycbcr_format.cb_x_position = 0.5f;
107 ycbcr_format.cb_y_position = 0.5f;
108 ycbcr_format.cr_x_position = 0.5f;
109 ycbcr_format.cr_y_position = 0.5f;
111 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
112 input->set_pixel_data(0, y);
113 input->set_pixel_data(1, cb);
114 input->set_pixel_data(2, cr);
115 tester.get_chain()->add_input(input);
117 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
119 // Y'CbCr isn't 100% accurate (the input values are rounded),
120 // so we need some leeway.
121 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
124 TEST(YCbCrInputTest, Rec709) {
126 const int height = 5;
128 // Pure-color test inputs, calculated with the formulas in Rec. 709
129 // page 19, items 3.4 and 3.5.
130 unsigned char y[width * height] = {
131 16, 235, 63, 173, 32,
133 unsigned char cb[width * height] = {
134 128, 128, 102, 42, 240,
136 unsigned char cr[width * height] = {
137 128, 128, 240, 26, 118,
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_709;
156 ycbcr_format.full_range = false;
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, Rec2020) {
180 const int height = 5;
182 // Pure-color test inputs, calculated with the formulas in Rec. 2020
183 // page 4, tables 4 and 5 (for conventional non-constant luminance).
184 // Note that we still use 8-bit inputs, even though Rec. 2020 is only
185 // defined for 10- and 12-bit.
186 unsigned char y[width * height] = {
187 16, 235, 74, 164, 29,
189 unsigned char cb[width * height] = {
190 128, 128, 97, 47, 240,
192 unsigned char cr[width * height] = {
193 128, 128, 240, 25, 119,
195 float expected_data[4 * width * height] = {
202 float out_data[4 * width * height];
204 EffectChainTester tester(NULL, width, height);
207 format.color_space = COLORSPACE_sRGB;
208 format.gamma_curve = GAMMA_sRGB;
210 YCbCrFormat ycbcr_format;
211 ycbcr_format.luma_coefficients = YCBCR_REC_2020;
212 ycbcr_format.full_range = false;
213 ycbcr_format.num_levels = 256;
214 ycbcr_format.chroma_subsampling_x = 1;
215 ycbcr_format.chroma_subsampling_y = 1;
216 ycbcr_format.cb_x_position = 0.5f;
217 ycbcr_format.cb_y_position = 0.5f;
218 ycbcr_format.cr_x_position = 0.5f;
219 ycbcr_format.cr_y_position = 0.5f;
221 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
222 input->set_pixel_data(0, y);
223 input->set_pixel_data(1, cb);
224 input->set_pixel_data(2, cr);
225 tester.get_chain()->add_input(input);
227 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
229 // Y'CbCr isn't 100% accurate (the input values are rounded),
230 // so we need some leeway.
231 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
234 TEST(YCbCrInputTest, Subsampling420) {
236 const int height = 4;
238 unsigned char y[width * height] = {
244 unsigned char cb[(width/2) * (height/2)] = {
248 unsigned char cr[(width/2) * (height/2)] = {
253 // Note: This is only the blue channel. The chroma samples (with associated
254 // values for blue) are marked off in comments.
255 float expected_data[width * height] = {
256 0.000, 0.125, 0.375, 0.500,
258 0.125, 0.250, 0.500, 0.625,
260 0.375, 0.500, 0.750, 0.875,
262 0.500, 0.625, 0.875, 1.000,
264 float out_data[width * height];
266 EffectChainTester tester(NULL, width, height);
269 format.color_space = COLORSPACE_sRGB;
270 format.gamma_curve = GAMMA_sRGB;
272 YCbCrFormat ycbcr_format;
273 ycbcr_format.luma_coefficients = YCBCR_REC_601;
274 ycbcr_format.full_range = false;
275 ycbcr_format.num_levels = 256;
276 ycbcr_format.chroma_subsampling_x = 2;
277 ycbcr_format.chroma_subsampling_y = 2;
278 ycbcr_format.cb_x_position = 0.5f;
279 ycbcr_format.cb_y_position = 0.5f;
280 ycbcr_format.cr_x_position = 0.5f;
281 ycbcr_format.cr_y_position = 0.5f;
283 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
284 input->set_pixel_data(0, y);
285 input->set_pixel_data(1, cb);
286 input->set_pixel_data(2, cr);
287 tester.get_chain()->add_input(input);
289 tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
291 // Y'CbCr isn't 100% accurate (the input values are rounded),
292 // so we need some leeway.
293 expect_equal(expected_data, out_data, width, height, 0.01, 0.001);
296 TEST(YCbCrInputTest, Subsampling420WithNonCenteredSamples) {
298 const int height = 4;
300 unsigned char y[width * height] = {
306 unsigned char cb[(width/2) * (height/2)] = {
310 unsigned char cr[(width/2) * (height/2)] = {
315 // Note: This is only the blue channel. The chroma samples (with associated
316 // values for blue) are marked off in comments.
317 float expected_data[width * height] = {
318 0.000, 0.250, 0.500, 0.500,
320 0.125, 0.375, 0.625, 0.625,
322 0.375, 0.625, 0.875, 0.875,
324 0.500, 0.750, 1.000, 1.000,
326 float out_data[width * height];
328 EffectChainTester tester(NULL, width, height);
331 format.color_space = COLORSPACE_sRGB;
332 format.gamma_curve = GAMMA_sRGB;
334 YCbCrFormat ycbcr_format;
335 ycbcr_format.luma_coefficients = YCBCR_REC_601;
336 ycbcr_format.full_range = false;
337 ycbcr_format.num_levels = 256;
338 ycbcr_format.chroma_subsampling_x = 2;
339 ycbcr_format.chroma_subsampling_y = 2;
340 ycbcr_format.cb_x_position = 0.0f;
341 ycbcr_format.cb_y_position = 0.5f;
342 ycbcr_format.cr_x_position = 0.0f;
343 ycbcr_format.cr_y_position = 0.5f;
345 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
346 input->set_pixel_data(0, y);
347 input->set_pixel_data(1, cb);
348 input->set_pixel_data(2, cr);
349 tester.get_chain()->add_input(input);
351 tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
353 // Y'CbCr isn't 100% accurate (the input values are rounded),
354 // so we need some leeway.
355 expect_equal(expected_data, out_data, width, height, 0.01, 0.001);
358 // Yes, some 4:2:2 formats actually have this craziness.
359 TEST(YCbCrInputTest, DifferentCbAndCrPositioning) {
361 const int height = 4;
363 unsigned char y[width * height] = {
369 unsigned char cb[(width/2) * height] = {
375 unsigned char cr[(width/2) * height] = {
382 // Chroma samples in this csae are always co-sited with a luma sample;
383 // their associated color values and position are marked off in comments.
384 float expected_data_blue[width * height] = {
385 0.000 /* 0.0 */, 0.250, 0.500 /* 0.5 */, 0.500,
386 0.500 /* 0.5 */, 0.750, 1.000 /* 1.0 */, 1.000,
387 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */, 0.500,
388 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */, 0.500,
390 float expected_data_red[width * height] = {
391 0.000, 0.000 /* 0.0 */, 0.250, 0.500 /* 0.5 */,
392 0.500, 0.500 /* 0.5 */, 0.750, 1.000 /* 1.0 */,
393 0.500, 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */,
394 0.500, 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */,
396 float out_data[width * height];
398 EffectChainTester tester(NULL, width, height);
401 format.color_space = COLORSPACE_sRGB;
402 format.gamma_curve = GAMMA_sRGB;
404 YCbCrFormat ycbcr_format;
405 ycbcr_format.luma_coefficients = YCBCR_REC_601;
406 ycbcr_format.full_range = false;
407 ycbcr_format.num_levels = 256;
408 ycbcr_format.chroma_subsampling_x = 2;
409 ycbcr_format.chroma_subsampling_y = 1;
410 ycbcr_format.cb_x_position = 0.0f;
411 ycbcr_format.cb_y_position = 0.5f;
412 ycbcr_format.cr_x_position = 1.0f;
413 ycbcr_format.cr_y_position = 0.5f;
415 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
416 input->set_pixel_data(0, y);
417 input->set_pixel_data(1, cb);
418 input->set_pixel_data(2, cr);
419 tester.get_chain()->add_input(input);
421 // Y'CbCr isn't 100% accurate (the input values are rounded),
422 // so we need some leeway.
423 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
424 expect_equal(expected_data_red, out_data, width, height, 0.02, 0.002);
426 tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
427 expect_equal(expected_data_blue, out_data, width, height, 0.01, 0.001);
430 TEST(YCbCrInputTest, PBO) {
432 const int height = 5;
434 // Pure-color test inputs, calculated with the formulas in Rec. 601
436 unsigned char data[width * height * 3] = {
437 16, 235, 81, 145, 41,
438 128, 128, 90, 54, 240,
439 128, 128, 240, 34, 110,
441 float expected_data[4 * width * height] = {
448 float out_data[4 * width * height];
451 glGenBuffers(1, &pbo);
452 glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo);
453 glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, width * height * 3, data, GL_STREAM_DRAW);
454 glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
456 EffectChainTester tester(NULL, 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 = 1;
467 ycbcr_format.chroma_subsampling_y = 1;
468 ycbcr_format.cb_x_position = 0.5f;
469 ycbcr_format.cb_y_position = 0.5f;
470 ycbcr_format.cr_x_position = 0.5f;
471 ycbcr_format.cr_y_position = 0.5f;
473 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
474 input->set_pixel_data(0, (unsigned char *)BUFFER_OFFSET(0), pbo);
475 input->set_pixel_data(1, (unsigned char *)BUFFER_OFFSET(width * height), pbo);
476 input->set_pixel_data(2, (unsigned char *)BUFFER_OFFSET(width * height * 2), pbo);
477 tester.get_chain()->add_input(input);
479 tester.run(out_data, GL_RGBA, 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, 4 * width, height, 0.025, 0.002);
485 glDeleteBuffers(1, &pbo);
488 TEST(YCbCrInputTest, CombinedCbAndCr) {
490 const int height = 5;
492 // Pure-color test inputs, calculated with the formulas in Rec. 601
494 unsigned char y[width * height] = {
495 16, 235, 81, 145, 41,
497 unsigned char cb_cr[width * height * 2] = {
504 float expected_data[4 * width * height] = {
511 float out_data[4 * width * height];
513 EffectChainTester tester(NULL, width, height);
516 format.color_space = COLORSPACE_sRGB;
517 format.gamma_curve = GAMMA_sRGB;
519 YCbCrFormat ycbcr_format;
520 ycbcr_format.luma_coefficients = YCBCR_REC_601;
521 ycbcr_format.full_range = false;
522 ycbcr_format.num_levels = 256;
523 ycbcr_format.chroma_subsampling_x = 1;
524 ycbcr_format.chroma_subsampling_y = 1;
525 ycbcr_format.cb_x_position = 0.5f;
526 ycbcr_format.cb_y_position = 0.5f;
527 ycbcr_format.cr_x_position = 0.5f;
528 ycbcr_format.cr_y_position = 0.5f;
530 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_SPLIT_Y_AND_CBCR);
531 input->set_pixel_data(0, y);
532 input->set_pixel_data(1, cb_cr);
533 tester.get_chain()->add_input(input);
535 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
537 // Y'CbCr isn't 100% accurate (the input values are rounded),
538 // so we need some leeway.
539 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
542 TEST(YCbCrInputTest, ExternalTexture) {
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[width * height] = {
552 128, 128, 90, 54, 240,
554 unsigned char cr[width * height] = {
555 128, 128, 240, 34, 110,
557 float expected_data[4 * width * height] = {
564 float out_data[4 * width * height];
566 EffectChainTester tester(NULL, width, height);
569 format.color_space = COLORSPACE_sRGB;
570 format.gamma_curve = GAMMA_sRGB;
572 YCbCrFormat ycbcr_format;
573 ycbcr_format.luma_coefficients = YCBCR_REC_601;
574 ycbcr_format.full_range = false;
575 ycbcr_format.num_levels = 256;
576 ycbcr_format.chroma_subsampling_x = 1;
577 ycbcr_format.chroma_subsampling_y = 1;
578 ycbcr_format.cb_x_position = 0.5f;
579 ycbcr_format.cb_y_position = 0.5f;
580 ycbcr_format.cr_x_position = 0.5f;
581 ycbcr_format.cr_y_position = 0.5f;
583 // Make a texture for the Cb data; keep the others as regular uploads.
585 GLuint cb_tex = pool.create_2d_texture(GL_R8, width, height);
587 glBindTexture(GL_TEXTURE_2D, cb_tex);
589 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
591 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
593 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, cb);
595 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
597 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
600 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
601 input->set_pixel_data(0, y);
602 input->set_texture_num(1, cb_tex);
603 input->set_pixel_data(2, cr);
604 tester.get_chain()->add_input(input);
606 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
608 pool.release_2d_texture(cb_tex);
610 // Y'CbCr isn't 100% accurate (the input values are rounded),
611 // so we need some leeway.
612 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);