1 // Unit tests for YCbCrInput.
2 // FIXME: This class really ought to support mipmaps.
7 #include "effect_chain.h"
8 #include "gtest/gtest.h"
11 #include "ycbcr_input.h"
15 TEST(YCbCrInput, 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.chroma_subsampling_x = 1;
49 ycbcr_format.chroma_subsampling_y = 1;
50 ycbcr_format.cb_x_position = 0.5f;
51 ycbcr_format.cb_y_position = 0.5f;
52 ycbcr_format.cr_x_position = 0.5f;
53 ycbcr_format.cr_y_position = 0.5f;
55 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
56 input->set_pixel_data(0, y);
57 input->set_pixel_data(1, cb);
58 input->set_pixel_data(2, cr);
59 tester.get_chain()->add_input(input);
61 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
63 // Y'CbCr isn't 100% accurate (the input values are rounded),
64 // so we need some leeway.
65 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
68 TEST(YCbCrInput, FullRangeRec601) {
72 // Pure-color test inputs, calculated with the formulas in Rec. 601
73 // section 2.5.4 but without the scaling factors applied
74 // (so both R, G, B, Y, Cb and R vary from 0 to 255).
75 unsigned char y[width * height] = {
78 unsigned char cb[width * height] = {
79 128, 128, 85, 44, 255,
81 unsigned char cr[width * height] = {
82 128, 128, 255, 21, 107,
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 = true;
102 ycbcr_format.chroma_subsampling_x = 1;
103 ycbcr_format.chroma_subsampling_y = 1;
104 ycbcr_format.cb_x_position = 0.5f;
105 ycbcr_format.cb_y_position = 0.5f;
106 ycbcr_format.cr_x_position = 0.5f;
107 ycbcr_format.cr_y_position = 0.5f;
109 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
110 input->set_pixel_data(0, y);
111 input->set_pixel_data(1, cb);
112 input->set_pixel_data(2, cr);
113 tester.get_chain()->add_input(input);
115 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
117 // Y'CbCr isn't 100% accurate (the input values are rounded),
118 // so we need some leeway.
119 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
122 TEST(YCbCrInput, Rec709) {
124 const int height = 5;
126 // Pure-color test inputs, calculated with the formulas in Rec. 709
127 // page 19, items 3.4 and 3.5.
128 unsigned char y[width * height] = {
129 16, 235, 63, 173, 32,
131 unsigned char cb[width * height] = {
132 128, 128, 102, 42, 240,
134 unsigned char cr[width * height] = {
135 128, 128, 240, 26, 118,
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_709;
154 ycbcr_format.full_range = false;
155 ycbcr_format.chroma_subsampling_x = 1;
156 ycbcr_format.chroma_subsampling_y = 1;
157 ycbcr_format.cb_x_position = 0.5f;
158 ycbcr_format.cb_y_position = 0.5f;
159 ycbcr_format.cr_x_position = 0.5f;
160 ycbcr_format.cr_y_position = 0.5f;
162 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
163 input->set_pixel_data(0, y);
164 input->set_pixel_data(1, cb);
165 input->set_pixel_data(2, cr);
166 tester.get_chain()->add_input(input);
168 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
170 // Y'CbCr isn't 100% accurate (the input values are rounded),
171 // so we need some leeway.
172 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
175 TEST(YCbCrInput, Rec2020) {
177 const int height = 5;
179 // Pure-color test inputs, calculated with the formulas in Rec. 2020
180 // page 4, tables 4 and 5 (for conventional non-constant luminance).
181 // Note that we still use 8-bit inputs, even though Rec. 2020 is only
182 // defined for 10- and 12-bit.
183 unsigned char y[width * height] = {
184 16, 235, 74, 164, 29,
186 unsigned char cb[width * height] = {
187 128, 128, 97, 47, 240,
189 unsigned char cr[width * height] = {
190 128, 128, 240, 25, 119,
192 float expected_data[4 * width * height] = {
199 float out_data[4 * width * height];
201 EffectChainTester tester(NULL, width, height);
204 format.color_space = COLORSPACE_sRGB;
205 format.gamma_curve = GAMMA_sRGB;
207 YCbCrFormat ycbcr_format;
208 ycbcr_format.luma_coefficients = YCBCR_REC_2020;
209 ycbcr_format.full_range = false;
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(YCbCrInput, Subsampling420) {
232 const int height = 4;
234 unsigned char y[width * height] = {
240 unsigned char cb[(width/2) * (height/2)] = {
244 unsigned char cr[(width/2) * (height/2)] = {
249 // Note: This is only the blue channel. The chroma samples (with associated
250 // values for blue) are marked off in comments.
251 float expected_data[width * height] = {
252 0.000, 0.125, 0.375, 0.500,
254 0.125, 0.250, 0.500, 0.625,
256 0.375, 0.500, 0.750, 0.875,
258 0.500, 0.625, 0.875, 1.000,
260 float out_data[width * height];
262 EffectChainTester tester(NULL, width, height);
265 format.color_space = COLORSPACE_sRGB;
266 format.gamma_curve = GAMMA_sRGB;
268 YCbCrFormat ycbcr_format;
269 ycbcr_format.luma_coefficients = YCBCR_REC_601;
270 ycbcr_format.full_range = false;
271 ycbcr_format.chroma_subsampling_x = 2;
272 ycbcr_format.chroma_subsampling_y = 2;
273 ycbcr_format.cb_x_position = 0.5f;
274 ycbcr_format.cb_y_position = 0.5f;
275 ycbcr_format.cr_x_position = 0.5f;
276 ycbcr_format.cr_y_position = 0.5f;
278 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
279 input->set_pixel_data(0, y);
280 input->set_pixel_data(1, cb);
281 input->set_pixel_data(2, cr);
282 tester.get_chain()->add_input(input);
284 tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
286 // Y'CbCr isn't 100% accurate (the input values are rounded),
287 // so we need some leeway.
288 expect_equal(expected_data, out_data, width, height, 0.01, 0.001);
291 TEST(YCbCrInput, Subsampling420WithNonCenteredSamples) {
293 const int height = 4;
295 unsigned char y[width * height] = {
301 unsigned char cb[(width/2) * (height/2)] = {
305 unsigned char cr[(width/2) * (height/2)] = {
310 // Note: This is only the blue channel. The chroma samples (with associated
311 // values for blue) are marked off in comments.
312 float expected_data[width * height] = {
313 0.000, 0.250, 0.500, 0.500,
315 0.125, 0.375, 0.625, 0.625,
317 0.375, 0.625, 0.875, 0.875,
319 0.500, 0.750, 1.000, 1.000,
321 float out_data[width * height];
323 EffectChainTester tester(NULL, width, height);
326 format.color_space = COLORSPACE_sRGB;
327 format.gamma_curve = GAMMA_sRGB;
329 YCbCrFormat ycbcr_format;
330 ycbcr_format.luma_coefficients = YCBCR_REC_601;
331 ycbcr_format.full_range = false;
332 ycbcr_format.chroma_subsampling_x = 2;
333 ycbcr_format.chroma_subsampling_y = 2;
334 ycbcr_format.cb_x_position = 0.0f;
335 ycbcr_format.cb_y_position = 0.5f;
336 ycbcr_format.cr_x_position = 0.0f;
337 ycbcr_format.cr_y_position = 0.5f;
339 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
340 input->set_pixel_data(0, y);
341 input->set_pixel_data(1, cb);
342 input->set_pixel_data(2, cr);
343 tester.get_chain()->add_input(input);
345 tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
347 // Y'CbCr isn't 100% accurate (the input values are rounded),
348 // so we need some leeway.
349 expect_equal(expected_data, out_data, width, height, 0.01, 0.001);
352 // Yes, some 4:2:2 formats actually have this craziness.
353 TEST(YCbCrInput, DifferentCbAndCrPositioning) {
355 const int height = 4;
357 unsigned char y[width * height] = {
363 unsigned char cb[(width/2) * height] = {
369 unsigned char cr[(width/2) * height] = {
376 // Chroma samples in this csae are always co-sited with a luma sample;
377 // their associated color values and position are marked off in comments.
378 float expected_data_blue[width * height] = {
379 0.000 /* 0.0 */, 0.250, 0.500 /* 0.5 */, 0.500,
380 0.500 /* 0.5 */, 0.750, 1.000 /* 1.0 */, 1.000,
381 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */, 0.500,
382 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */, 0.500,
384 float expected_data_red[width * height] = {
385 0.000, 0.000 /* 0.0 */, 0.250, 0.500 /* 0.5 */,
386 0.500, 0.500 /* 0.5 */, 0.750, 1.000 /* 1.0 */,
387 0.500, 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */,
388 0.500, 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */,
390 float out_data[width * height];
392 EffectChainTester tester(NULL, width, height);
395 format.color_space = COLORSPACE_sRGB;
396 format.gamma_curve = GAMMA_sRGB;
398 YCbCrFormat ycbcr_format;
399 ycbcr_format.luma_coefficients = YCBCR_REC_601;
400 ycbcr_format.full_range = false;
401 ycbcr_format.chroma_subsampling_x = 2;
402 ycbcr_format.chroma_subsampling_y = 1;
403 ycbcr_format.cb_x_position = 0.0f;
404 ycbcr_format.cb_y_position = 0.5f;
405 ycbcr_format.cr_x_position = 1.0f;
406 ycbcr_format.cr_y_position = 0.5f;
408 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
409 input->set_pixel_data(0, y);
410 input->set_pixel_data(1, cb);
411 input->set_pixel_data(2, cr);
412 tester.get_chain()->add_input(input);
414 // Y'CbCr isn't 100% accurate (the input values are rounded),
415 // so we need some leeway.
416 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
417 expect_equal(expected_data_red, out_data, width, height, 0.02, 0.002);
419 tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
420 expect_equal(expected_data_blue, out_data, width, height, 0.01, 0.001);
423 TEST(YCbCrInput, PBO) {
425 const int height = 5;
427 // Pure-color test inputs, calculated with the formulas in Rec. 601
429 unsigned char data[width * height * 3] = {
430 16, 235, 81, 145, 41,
431 128, 128, 90, 54, 240,
432 128, 128, 240, 34, 110,
434 float expected_data[4 * width * height] = {
441 float out_data[4 * width * height];
444 glGenBuffers(1, &pbo);
445 glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo);
446 glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, width * height * 3, data, GL_STREAM_DRAW);
447 glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
449 EffectChainTester tester(NULL, width, height);
452 format.color_space = COLORSPACE_sRGB;
453 format.gamma_curve = GAMMA_sRGB;
455 YCbCrFormat ycbcr_format;
456 ycbcr_format.luma_coefficients = YCBCR_REC_601;
457 ycbcr_format.full_range = false;
458 ycbcr_format.chroma_subsampling_x = 1;
459 ycbcr_format.chroma_subsampling_y = 1;
460 ycbcr_format.cb_x_position = 0.5f;
461 ycbcr_format.cb_y_position = 0.5f;
462 ycbcr_format.cr_x_position = 0.5f;
463 ycbcr_format.cr_y_position = 0.5f;
465 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
466 input->set_pixel_data(0, (unsigned char *)BUFFER_OFFSET(0), pbo);
467 input->set_pixel_data(1, (unsigned char *)BUFFER_OFFSET(width * height), pbo);
468 input->set_pixel_data(2, (unsigned char *)BUFFER_OFFSET(width * height * 2), pbo);
469 tester.get_chain()->add_input(input);
471 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
473 // Y'CbCr isn't 100% accurate (the input values are rounded),
474 // so we need some leeway.
475 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
477 glDeleteBuffers(1, &pbo);