1 // Unit tests for YCbCrConversionEffect. Mostly done by leveraging
2 // YCbCrInput and seeing that the right thing comes out at the
8 #include "effect_chain.h"
9 #include "gtest/gtest.h"
10 #include "image_format.h"
11 #include "test_util.h"
13 #include "ycbcr_input.h"
17 TEST(YCbCrConversionEffectTest, BasicInOut) {
21 // Pure-color test inputs, calculated with the formulas in Rec. 601
23 unsigned char y[width * height] = {
26 unsigned char cb[width * height] = {
27 128, 128, 90, 54, 240,
29 unsigned char cr[width * height] = {
30 128, 128, 240, 34, 110,
32 unsigned char expected_data[width * height * 4] = {
33 // The same data, just rearranged.
41 unsigned char out_data[width * height * 4];
43 EffectChainTester tester(NULL, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA8);
46 format.color_space = COLORSPACE_sRGB;
47 format.gamma_curve = GAMMA_sRGB;
49 YCbCrFormat ycbcr_format;
50 ycbcr_format.luma_coefficients = YCBCR_REC_601;
51 ycbcr_format.full_range = false;
52 ycbcr_format.num_levels = 256;
53 ycbcr_format.chroma_subsampling_x = 1;
54 ycbcr_format.chroma_subsampling_y = 1;
55 ycbcr_format.cb_x_position = 0.5f;
56 ycbcr_format.cb_y_position = 0.5f;
57 ycbcr_format.cr_x_position = 0.5f;
58 ycbcr_format.cr_y_position = 0.5f;
60 tester.add_ycbcr_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_format);
62 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
63 input->set_pixel_data(0, y);
64 input->set_pixel_data(1, cb);
65 input->set_pixel_data(2, cr);
66 tester.get_chain()->add_input(input);
68 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
69 expect_equal(expected_data, out_data, 4 * width, height);
72 TEST(YCbCrConversionEffectTest, ClampToValidRange) {
76 // Some out-of-range of at-range values.
77 // Y should be clamped to 16-235 and Cb/Cr to 16-240.
78 // (Alpha should still be 255.)
79 unsigned char y[width * height] = {
80 0, 10, 16, 235, 240, 255
82 unsigned char cb[width * height] = {
83 0, 10, 16, 235, 240, 255,
85 unsigned char cr[width * height] = {
86 255, 240, 235, 16, 10, 0,
88 unsigned char expected_data[width * height * 4] = {
97 unsigned char out_data[width * height * 4];
99 EffectChainTester tester(NULL, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA8);
102 format.color_space = COLORSPACE_sRGB;
103 format.gamma_curve = GAMMA_sRGB;
105 YCbCrFormat ycbcr_format;
106 ycbcr_format.luma_coefficients = YCBCR_REC_601;
107 ycbcr_format.full_range = false;
108 ycbcr_format.num_levels = 256;
109 ycbcr_format.chroma_subsampling_x = 1;
110 ycbcr_format.chroma_subsampling_y = 1;
111 ycbcr_format.cb_x_position = 0.5f;
112 ycbcr_format.cb_y_position = 0.5f;
113 ycbcr_format.cr_x_position = 0.5f;
114 ycbcr_format.cr_y_position = 0.5f;
116 tester.add_ycbcr_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_format);
118 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
119 input->set_pixel_data(0, y);
120 input->set_pixel_data(1, cb);
121 input->set_pixel_data(2, cr);
122 tester.get_chain()->add_input(input);
124 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
125 expect_equal(expected_data, out_data, 4 * width, height);
128 TEST(YCbCrConversionEffectTest, LimitedRangeToFullRange) {
130 const int height = 5;
132 // Pure-color test inputs, calculated with the formulas in Rec. 601
134 unsigned char y[width * height] = {
135 16, 235, 81, 145, 41,
137 unsigned char cb[width * height] = {
138 128, 128, 90, 54, 240,
140 unsigned char cr[width * height] = {
141 128, 128, 240, 34, 110,
143 unsigned char expected_data[width * height * 4] = {
144 // Range now from 0-255 for all components, and values in-between
145 // also adjusted a bit.
153 unsigned char out_data[width * height * 4];
155 EffectChainTester tester(NULL, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA8);
158 format.color_space = COLORSPACE_sRGB;
159 format.gamma_curve = GAMMA_sRGB;
161 YCbCrFormat ycbcr_format;
162 ycbcr_format.luma_coefficients = YCBCR_REC_601;
163 ycbcr_format.full_range = true;
164 ycbcr_format.num_levels = 256;
165 ycbcr_format.chroma_subsampling_x = 1;
166 ycbcr_format.chroma_subsampling_y = 1;
167 ycbcr_format.cb_x_position = 0.5f;
168 ycbcr_format.cb_y_position = 0.5f;
169 ycbcr_format.cr_x_position = 0.5f;
170 ycbcr_format.cr_y_position = 0.5f;
172 tester.add_ycbcr_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_format);
174 ycbcr_format.full_range = false;
175 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
176 input->set_pixel_data(0, y);
177 input->set_pixel_data(1, cb);
178 input->set_pixel_data(2, cr);
179 tester.get_chain()->add_input(input);
181 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
183 // This specific data element has the correct value (110-128)*(255/224) + 128 = 107.509,
184 // which rounds the wrong way on some cards. In normal use, we detect this and round off
185 // in DitherEffect instead (so it's not a problem in pratice), but in unit tests like this,
186 // we don't run with dither, so we simply fudge this one value instead.
187 if (out_data[18] == 107) {
191 expect_equal(expected_data, out_data, 4 * width, height);
194 TEST(YCbCrConversionEffectTest, PlanarOutput) {
196 const int height = 5;
198 // Pure-color test inputs, calculated with the formulas in Rec. 601
200 unsigned char y[width * height] = {
201 16, 235, 81, 145, 41,
203 unsigned char cb[width * height] = {
204 128, 128, 90, 54, 240,
206 unsigned char cr[width * height] = {
207 128, 128, 240, 34, 110,
210 unsigned char out_y[width * height], out_cb[width * height], out_cr[width * height];
212 EffectChainTester tester(NULL, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA8);
215 format.color_space = COLORSPACE_sRGB;
216 format.gamma_curve = GAMMA_sRGB;
218 YCbCrFormat ycbcr_format;
219 ycbcr_format.luma_coefficients = YCBCR_REC_601;
220 ycbcr_format.full_range = false;
221 ycbcr_format.num_levels = 256;
222 ycbcr_format.chroma_subsampling_x = 1;
223 ycbcr_format.chroma_subsampling_y = 1;
224 ycbcr_format.cb_x_position = 0.5f;
225 ycbcr_format.cb_y_position = 0.5f;
226 ycbcr_format.cr_x_position = 0.5f;
227 ycbcr_format.cr_y_position = 0.5f;
229 tester.add_ycbcr_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_format, YCBCR_OUTPUT_PLANAR);
231 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
232 input->set_pixel_data(0, y);
233 input->set_pixel_data(1, cb);
234 input->set_pixel_data(2, cr);
235 tester.get_chain()->add_input(input);
237 tester.run(out_y, out_cb, out_cr, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
238 expect_equal(y, out_y, width, height);
239 expect_equal(cb, out_cb, width, height);
240 expect_equal(cr, out_cr, width, height);
243 TEST(YCbCrConversionEffectTest, SplitLumaAndChroma) {
245 const int height = 5;
247 // Pure-color test inputs, calculated with the formulas in Rec. 601
249 unsigned char y[width * height] = {
250 16, 235, 81, 145, 41,
252 unsigned char cb[width * height] = {
253 128, 128, 90, 54, 240,
255 unsigned char cr[width * height] = {
256 128, 128, 240, 34, 110,
259 // The R and A data, rearranged. Note: The G and B channels
260 // (the middle columns) are undefined. If we change the behavior,
261 // the test will need to be updated, but a failure is expected.
262 unsigned char expected_y[width * height * 4] = {
263 16, /*undefined:*/ 16, /*undefined:*/ 16, 255,
264 235, /*undefined:*/ 235, /*undefined:*/ 235, 255,
265 81, /*undefined:*/ 81, /*undefined:*/ 81, 255,
266 145, /*undefined:*/ 145, /*undefined:*/ 145, 255,
267 41, /*undefined:*/ 41, /*undefined:*/ 41, 255,
270 // Just the Cb and Cr data, rearranged. The B and A channels
271 // are undefined, as below.
272 unsigned char expected_cbcr[width * height * 4] = {
273 128, 128, /*undefined:*/ 128, /*undefined:*/ 255,
274 128, 128, /*undefined:*/ 128, /*undefined:*/ 255,
275 90, 240, /*undefined:*/ 240, /*undefined:*/ 255,
276 54, 34, /*undefined:*/ 34, /*undefined:*/ 255,
277 240, 110, /*undefined:*/ 110, /*undefined:*/ 255,
280 unsigned char out_y[width * height * 4], out_cbcr[width * height * 4];
282 EffectChainTester tester(NULL, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA8);
285 format.color_space = COLORSPACE_sRGB;
286 format.gamma_curve = GAMMA_sRGB;
288 YCbCrFormat ycbcr_format;
289 ycbcr_format.luma_coefficients = YCBCR_REC_601;
290 ycbcr_format.full_range = false;
291 ycbcr_format.num_levels = 256;
292 ycbcr_format.chroma_subsampling_x = 1;
293 ycbcr_format.chroma_subsampling_y = 1;
294 ycbcr_format.cb_x_position = 0.5f;
295 ycbcr_format.cb_y_position = 0.5f;
296 ycbcr_format.cr_x_position = 0.5f;
297 ycbcr_format.cr_y_position = 0.5f;
299 tester.add_ycbcr_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_format, YCBCR_OUTPUT_SPLIT_Y_AND_CBCR);
301 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
302 input->set_pixel_data(0, y);
303 input->set_pixel_data(1, cb);
304 input->set_pixel_data(2, cr);
305 tester.get_chain()->add_input(input);
307 tester.run(out_y, out_cbcr, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
308 expect_equal(expected_y, out_y, width * 4, height);
309 expect_equal(expected_cbcr, out_cbcr, width * 4, height);
312 TEST(YCbCrConversionEffectTest, OutputChunkyAndRGBA) {
314 const int height = 5;
316 // Pure-color test inputs, calculated with the formulas in Rec. 601
318 unsigned char y[width * height] = {
319 16, 235, 81, 145, 41,
321 unsigned char cb[width * height] = {
322 128, 128, 90, 54, 240,
324 unsigned char cr[width * height] = {
325 128, 128, 240, 34, 110,
327 unsigned char expected_ycbcr[width * height * 4] = {
328 // The same data, just rearranged.
335 unsigned char expected_rgba[width * height * 4] = {
343 unsigned char out_ycbcr[width * height * 4];
344 unsigned char out_rgba[width * height * 4];
346 EffectChainTester tester(NULL, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA8);
349 format.color_space = COLORSPACE_sRGB;
350 format.gamma_curve = GAMMA_sRGB;
352 YCbCrFormat ycbcr_format;
353 ycbcr_format.luma_coefficients = YCBCR_REC_601;
354 ycbcr_format.full_range = false;
355 ycbcr_format.num_levels = 256;
356 ycbcr_format.chroma_subsampling_x = 1;
357 ycbcr_format.chroma_subsampling_y = 1;
358 ycbcr_format.cb_x_position = 0.5f;
359 ycbcr_format.cb_y_position = 0.5f;
360 ycbcr_format.cr_x_position = 0.5f;
361 ycbcr_format.cr_y_position = 0.5f;
363 tester.add_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
364 tester.add_ycbcr_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_format);
366 YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
367 input->set_pixel_data(0, y);
368 input->set_pixel_data(1, cb);
369 input->set_pixel_data(2, cr);
370 tester.get_chain()->add_input(input);
372 // Note: We don't test that the values actually get dithered,
373 // just that the shader compiles and doesn't mess up badly.
374 tester.get_chain()->set_dither_bits(8);
376 tester.run(out_ycbcr, out_rgba, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
377 expect_equal(expected_ycbcr, out_ycbcr, width * 4, height);
379 // Y'CbCr isn't 100% accurate (the input values are rounded),
380 // so we need some leeway.
381 expect_equal(expected_rgba, out_rgba, 4 * width, height, 7, 255 * 0.002);