Hard-assert on something that has bitten me too many times now.
[movit] / ycbcr_conversion_effect_test.cpp
1 // Unit tests for YCbCrConversionEffect. Mostly done by leveraging
2 // YCbCrInput and seeing that the right thing comes out at the
3 // other end.
4
5 #include <epoxy/gl.h>
6 #include <math.h>
7
8 #include "effect_chain.h"
9 #include "gtest/gtest.h"
10 #include "image_format.h"
11 #include "test_util.h"
12 #include "util.h"
13 #include "ycbcr_input.h"
14
15 namespace movit {
16
17 TEST(YCbCrConversionEffectTest, BasicInOut) {
18         const int width = 1;
19         const int height = 5;
20
21         // Pure-color test inputs, calculated with the formulas in Rec. 601
22         // section 2.5.4.
23         unsigned char y[width * height] = {
24                 16, 235, 81, 145, 41,
25         };
26         unsigned char cb[width * height] = {
27                 128, 128, 90, 54, 240,
28         };
29         unsigned char cr[width * height] = {
30                 128, 128, 240, 34, 110,
31         };
32         unsigned char expected_data[width * height * 4] = {
33                 // The same data, just rearranged.
34                  16, 128, 128, 255,
35                 235, 128, 128, 255,
36                  81,  90, 240, 255,
37                 145,  54,  34, 255,
38                  41, 240, 110, 255
39         };
40
41         unsigned char out_data[width * height * 4];
42
43         EffectChainTester tester(NULL, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA8);
44
45         ImageFormat format;
46         format.color_space = COLORSPACE_sRGB;
47         format.gamma_curve = GAMMA_sRGB;
48
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;
59
60         tester.add_ycbcr_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_format);
61
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);
67
68         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
69         expect_equal(expected_data, out_data, 4 * width, height);
70 }
71
72 TEST(YCbCrConversionEffectTest, ClampToValidRange) {
73         const int width = 1;
74         const int height = 6;
75
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
81         };
82         unsigned char cb[width * height] = {
83                 0, 10, 16, 235, 240, 255,
84         };
85         unsigned char cr[width * height] = {
86                 255, 240, 235, 16, 10, 0,
87         };
88         unsigned char expected_data[width * height * 4] = {
89                 16, 16, 240, 255,
90                 16, 16, 240, 255,
91                 16, 16, 235, 255,
92                 235, 235, 16, 255,
93                 235, 240, 16, 255,
94                 235, 240, 16, 255,
95         };
96
97         unsigned char out_data[width * height * 4];
98
99         EffectChainTester tester(NULL, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA8);
100
101         ImageFormat format;
102         format.color_space = COLORSPACE_sRGB;
103         format.gamma_curve = GAMMA_sRGB;
104
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;
115
116         tester.add_ycbcr_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_format);
117
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);
123
124         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
125         expect_equal(expected_data, out_data, 4 * width, height);
126 }
127
128 TEST(YCbCrConversionEffectTest, LimitedRangeToFullRange) {
129         const int width = 1;
130         const int height = 5;
131
132         // Pure-color test inputs, calculated with the formulas in Rec. 601
133         // section 2.5.4.
134         unsigned char y[width * height] = {
135                 16, 235, 81, 145, 41,
136         };
137         unsigned char cb[width * height] = {
138                 128, 128, 90, 54, 240,
139         };
140         unsigned char cr[width * height] = {
141                 128, 128, 240, 34, 110,
142         };
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.
146                   0, 128, 128, 255,
147                 255, 128, 128, 255,
148                  76,  85, 255, 255,
149                 150,  44,  21, 255,
150                  29, 255, 108, 255
151         };
152
153         unsigned char out_data[width * height * 4];
154
155         EffectChainTester tester(NULL, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA8);
156
157         ImageFormat format;
158         format.color_space = COLORSPACE_sRGB;
159         format.gamma_curve = GAMMA_sRGB;
160
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;
171
172         tester.add_ycbcr_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_format);
173
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);
180
181         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
182
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) {
188                 out_data[18] = 108;
189         }
190
191         expect_equal(expected_data, out_data, 4 * width, height);
192 }
193
194 TEST(YCbCrConversionEffectTest, PlanarOutput) {
195         const int width = 1;
196         const int height = 5;
197
198         // Pure-color test inputs, calculated with the formulas in Rec. 601
199         // section 2.5.4.
200         unsigned char y[width * height] = {
201                 16, 235, 81, 145, 41,
202         };
203         unsigned char cb[width * height] = {
204                 128, 128, 90, 54, 240,
205         };
206         unsigned char cr[width * height] = {
207                 128, 128, 240, 34, 110,
208         };
209
210         unsigned char out_y[width * height], out_cb[width * height], out_cr[width * height];
211
212         EffectChainTester tester(NULL, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA8);
213
214         ImageFormat format;
215         format.color_space = COLORSPACE_sRGB;
216         format.gamma_curve = GAMMA_sRGB;
217
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;
228
229         tester.add_ycbcr_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_format, YCBCR_OUTPUT_PLANAR);
230
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);
236
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);
241 }
242
243 TEST(YCbCrConversionEffectTest, SplitLumaAndChroma) {
244         const int width = 1;
245         const int height = 5;
246
247         // Pure-color test inputs, calculated with the formulas in Rec. 601
248         // section 2.5.4.
249         unsigned char y[width * height] = {
250                 16, 235, 81, 145, 41,
251         };
252         unsigned char cb[width * height] = {
253                 128, 128, 90, 54, 240,
254         };
255         unsigned char cr[width * height] = {
256                 128, 128, 240, 34, 110,
257         };
258
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,
268         };
269
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,
278         };
279
280         unsigned char out_y[width * height * 4], out_cbcr[width * height * 4];
281
282         EffectChainTester tester(NULL, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA8);
283
284         ImageFormat format;
285         format.color_space = COLORSPACE_sRGB;
286         format.gamma_curve = GAMMA_sRGB;
287
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;
298
299         tester.add_ycbcr_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_format, YCBCR_OUTPUT_SPLIT_Y_AND_CBCR);
300
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);
306
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);
310 }
311
312 TEST(YCbCrConversionEffectTest, OutputChunkyAndRGBA) {
313         const int width = 1;
314         const int height = 5;
315
316         // Pure-color test inputs, calculated with the formulas in Rec. 601
317         // section 2.5.4.
318         unsigned char y[width * height] = {
319                 16, 235, 81, 145, 41,
320         };
321         unsigned char cb[width * height] = {
322                 128, 128, 90, 54, 240,
323         };
324         unsigned char cr[width * height] = {
325                 128, 128, 240, 34, 110,
326         };
327         unsigned char expected_ycbcr[width * height * 4] = {
328                 // The same data, just rearranged.
329                  16, 128, 128, 255,
330                 235, 128, 128, 255,
331                  81,  90, 240, 255,
332                 145,  54,  34, 255,
333                  41, 240, 110, 255
334         };
335         unsigned char expected_rgba[width * height * 4] = {
336                   0,   0,   0, 255,
337                 255, 255, 255, 255,
338                 255,   0,   0, 255,
339                   0, 255,   0, 255,
340                   0,   0, 255, 255,
341         };
342
343         unsigned char out_ycbcr[width * height * 4];
344         unsigned char out_rgba[width * height * 4];
345
346         EffectChainTester tester(NULL, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA8);
347
348         ImageFormat format;
349         format.color_space = COLORSPACE_sRGB;
350         format.gamma_curve = GAMMA_sRGB;
351
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;
362
363         tester.add_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
364         tester.add_ycbcr_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_format);
365
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);
371
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);
375
376         tester.run(out_ycbcr, out_rgba, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
377         expect_equal(expected_ycbcr, out_ycbcr, width * 4, height);
378
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);
382 }
383
384 }  // namespace movit