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"
13 TEST(YCbCrInput, Simple444) {
17 // Pure-color test inputs, calculated with the formulas in Rec. 601
19 unsigned char y[width * height] = {
22 unsigned char cb[width * height] = {
23 128, 128, 90, 54, 240,
25 unsigned char cr[width * height] = {
26 128, 128, 240, 34, 110,
28 float expected_data[4 * width * height] = {
35 float out_data[4 * width * height];
37 EffectChainTester tester(NULL, width, height);
40 format.color_space = COLORSPACE_sRGB;
41 format.gamma_curve = GAMMA_sRGB;
43 YCbCrFormat ycbcr_format;
44 ycbcr_format.luma_coefficients = YCBCR_REC_601;
45 ycbcr_format.full_range = false;
46 ycbcr_format.chroma_subsampling_x = 1;
47 ycbcr_format.chroma_subsampling_y = 1;
48 ycbcr_format.cb_x_position = 0.5f;
49 ycbcr_format.cb_y_position = 0.5f;
50 ycbcr_format.cr_x_position = 0.5f;
51 ycbcr_format.cr_y_position = 0.5f;
53 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
54 input->set_pixel_data(0, y);
55 input->set_pixel_data(1, cb);
56 input->set_pixel_data(2, cr);
57 tester.get_chain()->add_input(input);
59 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
61 // Y'CbCr isn't 100% accurate (the input values are rounded),
62 // so we need some leeway.
63 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
66 TEST(YCbCrInput, FullRangeRec601) {
70 // Pure-color test inputs, calculated with the formulas in Rec. 601
71 // section 2.5.4 but without the scaling factors applied
72 // (so both R, G, B, Y, Cb and R vary from 0 to 255).
73 unsigned char y[width * height] = {
76 unsigned char cb[width * height] = {
77 128, 128, 85, 44, 255,
79 unsigned char cr[width * height] = {
80 128, 128, 255, 21, 107,
82 float expected_data[4 * width * height] = {
89 float out_data[4 * width * height];
91 EffectChainTester tester(NULL, width, height);
94 format.color_space = COLORSPACE_sRGB;
95 format.gamma_curve = GAMMA_sRGB;
97 YCbCrFormat ycbcr_format;
98 ycbcr_format.luma_coefficients = YCBCR_REC_601;
99 ycbcr_format.full_range = true;
100 ycbcr_format.chroma_subsampling_x = 1;
101 ycbcr_format.chroma_subsampling_y = 1;
102 ycbcr_format.cb_x_position = 0.5f;
103 ycbcr_format.cb_y_position = 0.5f;
104 ycbcr_format.cr_x_position = 0.5f;
105 ycbcr_format.cr_y_position = 0.5f;
107 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
108 input->set_pixel_data(0, y);
109 input->set_pixel_data(1, cb);
110 input->set_pixel_data(2, cr);
111 tester.get_chain()->add_input(input);
113 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
115 // Y'CbCr isn't 100% accurate (the input values are rounded),
116 // so we need some leeway.
117 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
120 TEST(YCbCrInput, Rec709) {
122 const int height = 5;
124 // Pure-color test inputs, calculated with the formulas in Rec. 709
125 // page 19, items 3.4 and 3.5.
126 unsigned char y[width * height] = {
127 16, 235, 63, 173, 32,
129 unsigned char cb[width * height] = {
130 128, 128, 102, 42, 240,
132 unsigned char cr[width * height] = {
133 128, 128, 240, 26, 118,
135 float expected_data[4 * width * height] = {
142 float out_data[4 * width * height];
144 EffectChainTester tester(NULL, width, height);
147 format.color_space = COLORSPACE_sRGB;
148 format.gamma_curve = GAMMA_sRGB;
150 YCbCrFormat ycbcr_format;
151 ycbcr_format.luma_coefficients = YCBCR_REC_709;
152 ycbcr_format.full_range = false;
153 ycbcr_format.chroma_subsampling_x = 1;
154 ycbcr_format.chroma_subsampling_y = 1;
155 ycbcr_format.cb_x_position = 0.5f;
156 ycbcr_format.cb_y_position = 0.5f;
157 ycbcr_format.cr_x_position = 0.5f;
158 ycbcr_format.cr_y_position = 0.5f;
160 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
161 input->set_pixel_data(0, y);
162 input->set_pixel_data(1, cb);
163 input->set_pixel_data(2, cr);
164 tester.get_chain()->add_input(input);
166 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
168 // Y'CbCr isn't 100% accurate (the input values are rounded),
169 // so we need some leeway.
170 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
173 TEST(YCbCrInput, Rec2020) {
175 const int height = 5;
177 // Pure-color test inputs, calculated with the formulas in Rec. 2020
178 // page 4, tables 4 and 5 (for conventional non-constant luminance).
179 // Note that we still use 8-bit inputs, even though Rec. 2020 is only
180 // defined for 10- and 12-bit.
181 unsigned char y[width * height] = {
182 16, 235, 74, 164, 29,
184 unsigned char cb[width * height] = {
185 128, 128, 97, 47, 240,
187 unsigned char cr[width * height] = {
188 128, 128, 240, 25, 119,
190 float expected_data[4 * width * height] = {
197 float out_data[4 * width * height];
199 EffectChainTester tester(NULL, width, height);
202 format.color_space = COLORSPACE_sRGB;
203 format.gamma_curve = GAMMA_sRGB;
205 YCbCrFormat ycbcr_format;
206 ycbcr_format.luma_coefficients = YCBCR_REC_2020;
207 ycbcr_format.full_range = false;
208 ycbcr_format.chroma_subsampling_x = 1;
209 ycbcr_format.chroma_subsampling_y = 1;
210 ycbcr_format.cb_x_position = 0.5f;
211 ycbcr_format.cb_y_position = 0.5f;
212 ycbcr_format.cr_x_position = 0.5f;
213 ycbcr_format.cr_y_position = 0.5f;
215 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
216 input->set_pixel_data(0, y);
217 input->set_pixel_data(1, cb);
218 input->set_pixel_data(2, cr);
219 tester.get_chain()->add_input(input);
221 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
223 // Y'CbCr isn't 100% accurate (the input values are rounded),
224 // so we need some leeway.
225 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
228 TEST(YCbCrInput, Subsampling420) {
230 const int height = 4;
232 unsigned char y[width * height] = {
238 unsigned char cb[(width/2) * (height/2)] = {
242 unsigned char cr[(width/2) * (height/2)] = {
247 // Note: This is only the blue channel. The chroma samples (with associated
248 // values for blue) are marked off in comments.
249 float expected_data[width * height] = {
250 0.000, 0.125, 0.375, 0.500,
252 0.125, 0.250, 0.500, 0.625,
254 0.375, 0.500, 0.750, 0.875,
256 0.500, 0.625, 0.875, 1.000,
258 float out_data[width * height];
260 EffectChainTester tester(NULL, width, height);
263 format.color_space = COLORSPACE_sRGB;
264 format.gamma_curve = GAMMA_sRGB;
266 YCbCrFormat ycbcr_format;
267 ycbcr_format.luma_coefficients = YCBCR_REC_601;
268 ycbcr_format.full_range = false;
269 ycbcr_format.chroma_subsampling_x = 2;
270 ycbcr_format.chroma_subsampling_y = 2;
271 ycbcr_format.cb_x_position = 0.5f;
272 ycbcr_format.cb_y_position = 0.5f;
273 ycbcr_format.cr_x_position = 0.5f;
274 ycbcr_format.cr_y_position = 0.5f;
276 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
277 input->set_pixel_data(0, y);
278 input->set_pixel_data(1, cb);
279 input->set_pixel_data(2, cr);
280 tester.get_chain()->add_input(input);
282 tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
284 // Y'CbCr isn't 100% accurate (the input values are rounded),
285 // so we need some leeway.
286 expect_equal(expected_data, out_data, width, height, 0.01, 0.001);
289 TEST(YCbCrInput, Subsampling420WithNonCenteredSamples) {
291 const int height = 4;
293 unsigned char y[width * height] = {
299 unsigned char cb[(width/2) * (height/2)] = {
303 unsigned char cr[(width/2) * (height/2)] = {
308 // Note: This is only the blue channel. The chroma samples (with associated
309 // values for blue) are marked off in comments.
310 float expected_data[width * height] = {
311 0.000, 0.250, 0.500, 0.500,
313 0.125, 0.375, 0.625, 0.625,
315 0.375, 0.625, 0.875, 0.875,
317 0.500, 0.750, 1.000, 1.000,
319 float out_data[width * height];
321 EffectChainTester tester(NULL, width, height);
324 format.color_space = COLORSPACE_sRGB;
325 format.gamma_curve = GAMMA_sRGB;
327 YCbCrFormat ycbcr_format;
328 ycbcr_format.luma_coefficients = YCBCR_REC_601;
329 ycbcr_format.full_range = false;
330 ycbcr_format.chroma_subsampling_x = 2;
331 ycbcr_format.chroma_subsampling_y = 2;
332 ycbcr_format.cb_x_position = 0.0f;
333 ycbcr_format.cb_y_position = 0.5f;
334 ycbcr_format.cr_x_position = 0.0f;
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 // Yes, some 4:2:2 formats actually have this craziness.
351 TEST(YCbCrInput, DifferentCbAndCrPositioning) {
353 const int height = 4;
355 unsigned char y[width * height] = {
361 unsigned char cb[(width/2) * height] = {
367 unsigned char cr[(width/2) * height] = {
374 // Chroma samples in this csae are always co-sited with a luma sample;
375 // their associated color values and position are marked off in comments.
376 float expected_data_blue[width * height] = {
377 0.000 /* 0.0 */, 0.250, 0.500 /* 0.5 */, 0.500,
378 0.500 /* 0.5 */, 0.750, 1.000 /* 1.0 */, 1.000,
379 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */, 0.500,
380 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */, 0.500,
382 float expected_data_red[width * height] = {
383 0.000, 0.000 /* 0.0 */, 0.250, 0.500 /* 0.5 */,
384 0.500, 0.500 /* 0.5 */, 0.750, 1.000 /* 1.0 */,
385 0.500, 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */,
386 0.500, 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */,
388 float out_data[width * height];
390 EffectChainTester tester(NULL, width, height);
393 format.color_space = COLORSPACE_sRGB;
394 format.gamma_curve = GAMMA_sRGB;
396 YCbCrFormat ycbcr_format;
397 ycbcr_format.luma_coefficients = YCBCR_REC_601;
398 ycbcr_format.full_range = false;
399 ycbcr_format.chroma_subsampling_x = 2;
400 ycbcr_format.chroma_subsampling_y = 1;
401 ycbcr_format.cb_x_position = 0.0f;
402 ycbcr_format.cb_y_position = 0.5f;
403 ycbcr_format.cr_x_position = 1.0f;
404 ycbcr_format.cr_y_position = 0.5f;
406 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
407 input->set_pixel_data(0, y);
408 input->set_pixel_data(1, cb);
409 input->set_pixel_data(2, cr);
410 tester.get_chain()->add_input(input);
412 // Y'CbCr isn't 100% accurate (the input values are rounded),
413 // so we need some leeway.
414 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
415 expect_equal(expected_data_red, out_data, width, height, 0.02, 0.002);
417 tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
418 expect_equal(expected_data_blue, out_data, width, height, 0.01, 0.001);
421 TEST(YCbCrInput, PBO) {
423 const int height = 5;
425 // Pure-color test inputs, calculated with the formulas in Rec. 601
427 unsigned char data[width * height * 3] = {
428 16, 235, 81, 145, 41,
429 128, 128, 90, 54, 240,
430 128, 128, 240, 34, 110,
432 float expected_data[4 * width * height] = {
439 float out_data[4 * width * height];
442 glGenBuffers(1, &pbo);
443 glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo);
444 glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, width * height * 3, data, GL_STREAM_DRAW);
445 glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
447 EffectChainTester tester(NULL, width, height);
450 format.color_space = COLORSPACE_sRGB;
451 format.gamma_curve = GAMMA_sRGB;
453 YCbCrFormat ycbcr_format;
454 ycbcr_format.luma_coefficients = YCBCR_REC_601;
455 ycbcr_format.full_range = false;
456 ycbcr_format.chroma_subsampling_x = 1;
457 ycbcr_format.chroma_subsampling_y = 1;
458 ycbcr_format.cb_x_position = 0.5f;
459 ycbcr_format.cb_y_position = 0.5f;
460 ycbcr_format.cr_x_position = 0.5f;
461 ycbcr_format.cr_y_position = 0.5f;
463 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
464 input->set_pixel_data(0, (unsigned char *)BUFFER_OFFSET(0), pbo);
465 input->set_pixel_data(1, (unsigned char *)BUFFER_OFFSET(width * height), pbo);
466 input->set_pixel_data(2, (unsigned char *)BUFFER_OFFSET(width * height * 2), pbo);
467 tester.get_chain()->add_input(input);
469 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
471 // Y'CbCr isn't 100% accurate (the input values are rounded),
472 // so we need some leeway.
473 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
475 glDeleteBuffers(1, &pbo);