1 // Unit tests for YCbCrInput.
2 // FIXME: This class really ought to support mipmaps.
6 #include "effect_chain.h"
7 #include "gtest/gtest.h"
9 #include "ycbcr_input.h"
12 TEST(YCbCrInput, Simple444) {
16 // Pure-color test inputs, calculated with the formulas in Rec. 601
18 unsigned char y[width * height] = {
21 unsigned char cb[width * height] = {
22 128, 128, 90, 54, 240,
24 unsigned char cr[width * height] = {
25 128, 128, 240, 34, 110,
27 float expected_data[4 * width * height] = {
34 float out_data[4 * width * height];
36 EffectChainTester tester(NULL, width, height);
39 format.color_space = COLORSPACE_sRGB;
40 format.gamma_curve = GAMMA_sRGB;
42 YCbCrFormat ycbcr_format;
43 ycbcr_format.luma_coefficients = YCBCR_REC_601;
44 ycbcr_format.full_range = false;
45 ycbcr_format.chroma_subsampling_x = 1;
46 ycbcr_format.chroma_subsampling_y = 1;
47 ycbcr_format.cb_x_position = 0.5f;
48 ycbcr_format.cb_y_position = 0.5f;
49 ycbcr_format.cr_x_position = 0.5f;
50 ycbcr_format.cr_y_position = 0.5f;
52 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
53 input->set_pixel_data(0, y);
54 input->set_pixel_data(1, cb);
55 input->set_pixel_data(2, cr);
56 tester.get_chain()->add_input(input);
58 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
60 // Y'CbCr isn't 100% accurate (the input values are rounded),
61 // so we need some leeway.
62 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
65 TEST(YCbCrInput, FullRangeRec601) {
69 // Pure-color test inputs, calculated with the formulas in Rec. 601
70 // section 2.5.4 but without the scaling factors applied
71 // (so both R, G, B, Y, Cb and R vary from 0 to 255).
72 unsigned char y[width * height] = {
75 unsigned char cb[width * height] = {
76 128, 128, 85, 44, 255,
78 unsigned char cr[width * height] = {
79 128, 128, 255, 21, 107,
81 float expected_data[4 * width * height] = {
88 float out_data[4 * width * height];
90 EffectChainTester tester(NULL, width, height);
93 format.color_space = COLORSPACE_sRGB;
94 format.gamma_curve = GAMMA_sRGB;
96 YCbCrFormat ycbcr_format;
97 ycbcr_format.luma_coefficients = YCBCR_REC_601;
98 ycbcr_format.full_range = true;
99 ycbcr_format.chroma_subsampling_x = 1;
100 ycbcr_format.chroma_subsampling_y = 1;
101 ycbcr_format.cb_x_position = 0.5f;
102 ycbcr_format.cb_y_position = 0.5f;
103 ycbcr_format.cr_x_position = 0.5f;
104 ycbcr_format.cr_y_position = 0.5f;
106 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
107 input->set_pixel_data(0, y);
108 input->set_pixel_data(1, cb);
109 input->set_pixel_data(2, cr);
110 tester.get_chain()->add_input(input);
112 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
114 // Y'CbCr isn't 100% accurate (the input values are rounded),
115 // so we need some leeway.
116 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
119 TEST(YCbCrInput, Rec709) {
121 const int height = 5;
123 // Pure-color test inputs, calculated with the formulas in Rec. 709
124 // page 19, items 3.4 and 3.5.
125 unsigned char y[width * height] = {
126 16, 235, 63, 173, 32,
128 unsigned char cb[width * height] = {
129 128, 128, 102, 42, 240,
131 unsigned char cr[width * height] = {
132 128, 128, 240, 26, 118,
134 float expected_data[4 * width * height] = {
141 float out_data[4 * width * height];
143 EffectChainTester tester(NULL, width, height);
146 format.color_space = COLORSPACE_sRGB;
147 format.gamma_curve = GAMMA_sRGB;
149 YCbCrFormat ycbcr_format;
150 ycbcr_format.luma_coefficients = YCBCR_REC_709;
151 ycbcr_format.full_range = false;
152 ycbcr_format.chroma_subsampling_x = 1;
153 ycbcr_format.chroma_subsampling_y = 1;
154 ycbcr_format.cb_x_position = 0.5f;
155 ycbcr_format.cb_y_position = 0.5f;
156 ycbcr_format.cr_x_position = 0.5f;
157 ycbcr_format.cr_y_position = 0.5f;
159 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
160 input->set_pixel_data(0, y);
161 input->set_pixel_data(1, cb);
162 input->set_pixel_data(2, cr);
163 tester.get_chain()->add_input(input);
165 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
167 // Y'CbCr isn't 100% accurate (the input values are rounded),
168 // so we need some leeway.
169 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
172 TEST(YCbCrInput, Rec2020) {
174 const int height = 5;
176 // Pure-color test inputs, calculated with the formulas in Rec. 2020
177 // page 4, tables 4 and 5 (for conventional non-constant luminance).
178 // Note that we still use 8-bit inputs, even though Rec. 2020 is only
179 // defined for 10- and 12-bit.
180 unsigned char y[width * height] = {
181 16, 235, 74, 164, 29,
183 unsigned char cb[width * height] = {
184 128, 128, 97, 47, 240,
186 unsigned char cr[width * height] = {
187 128, 128, 240, 25, 119,
189 float expected_data[4 * width * height] = {
196 float out_data[4 * width * height];
198 EffectChainTester tester(NULL, width, height);
201 format.color_space = COLORSPACE_sRGB;
202 format.gamma_curve = GAMMA_sRGB;
204 YCbCrFormat ycbcr_format;
205 ycbcr_format.luma_coefficients = YCBCR_REC_2020;
206 ycbcr_format.full_range = false;
207 ycbcr_format.chroma_subsampling_x = 1;
208 ycbcr_format.chroma_subsampling_y = 1;
209 ycbcr_format.cb_x_position = 0.5f;
210 ycbcr_format.cb_y_position = 0.5f;
211 ycbcr_format.cr_x_position = 0.5f;
212 ycbcr_format.cr_y_position = 0.5f;
214 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
215 input->set_pixel_data(0, y);
216 input->set_pixel_data(1, cb);
217 input->set_pixel_data(2, cr);
218 tester.get_chain()->add_input(input);
220 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
222 // Y'CbCr isn't 100% accurate (the input values are rounded),
223 // so we need some leeway.
224 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
227 TEST(YCbCrInput, Subsampling420) {
229 const int height = 4;
231 unsigned char y[width * height] = {
237 unsigned char cb[(width/2) * (height/2)] = {
241 unsigned char cr[(width/2) * (height/2)] = {
246 // Note: This is only the blue channel. The chroma samples (with associated
247 // values for blue) are marked off in comments.
248 float expected_data[width * height] = {
249 0.000, 0.125, 0.375, 0.500,
251 0.125, 0.250, 0.500, 0.625,
253 0.375, 0.500, 0.750, 0.875,
255 0.500, 0.625, 0.875, 1.000,
257 float out_data[width * height];
259 EffectChainTester tester(NULL, width, height);
262 format.color_space = COLORSPACE_sRGB;
263 format.gamma_curve = GAMMA_sRGB;
265 YCbCrFormat ycbcr_format;
266 ycbcr_format.luma_coefficients = YCBCR_REC_601;
267 ycbcr_format.full_range = false;
268 ycbcr_format.chroma_subsampling_x = 2;
269 ycbcr_format.chroma_subsampling_y = 2;
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_BLUE, 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, width, height, 0.01, 0.001);
288 TEST(YCbCrInput, Subsampling420WithNonCenteredSamples) {
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.250, 0.500, 0.500,
312 0.125, 0.375, 0.625, 0.625,
314 0.375, 0.625, 0.875, 0.875,
316 0.500, 0.750, 1.000, 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.chroma_subsampling_x = 2;
330 ycbcr_format.chroma_subsampling_y = 2;
331 ycbcr_format.cb_x_position = 0.0f;
332 ycbcr_format.cb_y_position = 0.5f;
333 ycbcr_format.cr_x_position = 0.0f;
334 ycbcr_format.cr_y_position = 0.5f;
336 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
337 input->set_pixel_data(0, y);
338 input->set_pixel_data(1, cb);
339 input->set_pixel_data(2, cr);
340 tester.get_chain()->add_input(input);
342 tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
344 // Y'CbCr isn't 100% accurate (the input values are rounded),
345 // so we need some leeway.
346 expect_equal(expected_data, out_data, width, height, 0.01, 0.001);
349 // Yes, some 4:2:2 formats actually have this craziness.
350 TEST(YCbCrInput, DifferentCbAndCrPositioning) {
352 const int height = 4;
354 unsigned char y[width * height] = {
360 unsigned char cb[(width/2) * height] = {
366 unsigned char cr[(width/2) * height] = {
373 // Chroma samples in this csae are always co-sited with a luma sample;
374 // their associated color values and position are marked off in comments.
375 float expected_data_blue[width * height] = {
376 0.000 /* 0.0 */, 0.250, 0.500 /* 0.5 */, 0.500,
377 0.500 /* 0.5 */, 0.750, 1.000 /* 1.0 */, 1.000,
378 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */, 0.500,
379 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */, 0.500,
381 float expected_data_red[width * height] = {
382 0.000, 0.000 /* 0.0 */, 0.250, 0.500 /* 0.5 */,
383 0.500, 0.500 /* 0.5 */, 0.750, 1.000 /* 1.0 */,
384 0.500, 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */,
385 0.500, 0.500 /* 0.5 */, 0.500, 0.500 /* 0.5 */,
387 float out_data[width * height];
389 EffectChainTester tester(NULL, width, height);
392 format.color_space = COLORSPACE_sRGB;
393 format.gamma_curve = GAMMA_sRGB;
395 YCbCrFormat ycbcr_format;
396 ycbcr_format.luma_coefficients = YCBCR_REC_601;
397 ycbcr_format.full_range = false;
398 ycbcr_format.chroma_subsampling_x = 2;
399 ycbcr_format.chroma_subsampling_y = 1;
400 ycbcr_format.cb_x_position = 0.0f;
401 ycbcr_format.cb_y_position = 0.5f;
402 ycbcr_format.cr_x_position = 1.0f;
403 ycbcr_format.cr_y_position = 0.5f;
405 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
406 input->set_pixel_data(0, y);
407 input->set_pixel_data(1, cb);
408 input->set_pixel_data(2, cr);
409 tester.get_chain()->add_input(input);
411 // Y'CbCr isn't 100% accurate (the input values are rounded),
412 // so we need some leeway.
413 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
414 expect_equal(expected_data_red, out_data, width, height, 0.02, 0.002);
416 tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
417 expect_equal(expected_data_blue, out_data, width, height, 0.01, 0.001);
420 TEST(YCbCrInput, PBO) {
422 const int height = 5;
424 // Pure-color test inputs, calculated with the formulas in Rec. 601
426 unsigned char data[width * height * 3] = {
427 16, 235, 81, 145, 41,
428 128, 128, 90, 54, 240,
429 128, 128, 240, 34, 110,
431 float expected_data[4 * width * height] = {
438 float out_data[4 * width * height];
441 glGenBuffers(1, &pbo);
442 glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo);
443 glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, width * height * 3, data, GL_STREAM_DRAW);
444 glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
446 EffectChainTester tester(NULL, width, height);
449 format.color_space = COLORSPACE_sRGB;
450 format.gamma_curve = GAMMA_sRGB;
452 YCbCrFormat ycbcr_format;
453 ycbcr_format.luma_coefficients = YCBCR_REC_601;
454 ycbcr_format.full_range = false;
455 ycbcr_format.chroma_subsampling_x = 1;
456 ycbcr_format.chroma_subsampling_y = 1;
457 ycbcr_format.cb_x_position = 0.5f;
458 ycbcr_format.cb_y_position = 0.5f;
459 ycbcr_format.cr_x_position = 0.5f;
460 ycbcr_format.cr_y_position = 0.5f;
462 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
463 input->set_pixel_data(0, (unsigned char *)BUFFER_OFFSET(0), pbo);
464 input->set_pixel_data(1, (unsigned char *)BUFFER_OFFSET(width * height), pbo);
465 input->set_pixel_data(2, (unsigned char *)BUFFER_OFFSET(width * height * 2), pbo);
466 tester.get_chain()->add_input(input);
468 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
470 // Y'CbCr isn't 100% accurate (the input values are rounded),
471 // so we need some leeway.
472 expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
474 glDeleteBuffers(1, &pbo);