In ResampleEffect, precompute the Lanczos function into a table.
[movit] / ycbcr_input_test.cpp
1 // Unit tests for YCbCrInput.
2
3 #include <epoxy/gl.h>
4 #include <stddef.h>
5
6 #include "effect_chain.h"
7 #include "gtest/gtest.h"
8 #include "test_util.h"
9 #include "util.h"
10 #include "ycbcr_input.h"
11
12 namespace movit {
13
14 TEST(YCbCrInputTest, Simple444) {
15         const int width = 1;
16         const int height = 5;
17
18         // Pure-color test inputs, calculated with the formulas in Rec. 601
19         // section 2.5.4.
20         unsigned char y[width * height] = {
21                 16, 235, 81, 145, 41,
22         };
23         unsigned char cb[width * height] = {
24                 128, 128, 90, 54, 240,
25         };
26         unsigned char cr[width * height] = {
27                 128, 128, 240, 34, 110,
28         };
29         float expected_data[4 * width * height] = {
30                 0.0, 0.0, 0.0, 1.0,
31                 1.0, 1.0, 1.0, 1.0,
32                 1.0, 0.0, 0.0, 1.0,
33                 0.0, 1.0, 0.0, 1.0,
34                 0.0, 0.0, 1.0, 1.0,
35         };
36         float out_data[4 * width * height];
37
38         EffectChainTester tester(NULL, width, height);
39
40         ImageFormat format;
41         format.color_space = COLORSPACE_sRGB;
42         format.gamma_curve = GAMMA_sRGB;
43
44         YCbCrFormat ycbcr_format;
45         ycbcr_format.luma_coefficients = YCBCR_REC_601;
46         ycbcr_format.full_range = false;
47         ycbcr_format.num_levels = 256;
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;
54
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);
60
61         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
62
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);
66 }
67
68 TEST(YCbCrInputTest, FullRangeRec601) {
69         const int width = 1;
70         const int height = 5;
71
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] = {
76                 0, 255, 76, 150, 29,
77         };
78         unsigned char cb[width * height] = {
79                 128, 128, 85, 44, 255,
80         };
81         unsigned char cr[width * height] = {
82                 128, 128, 255, 21, 107,
83         };
84         float expected_data[4 * width * height] = {
85                 0.0, 0.0, 0.0, 1.0,
86                 1.0, 1.0, 1.0, 1.0,
87                 1.0, 0.0, 0.0, 1.0,
88                 0.0, 1.0, 0.0, 1.0,
89                 0.0, 0.0, 1.0, 1.0,
90         };
91         float out_data[4 * width * height];
92
93         EffectChainTester tester(NULL, width, height);
94
95         ImageFormat format;
96         format.color_space = COLORSPACE_sRGB;
97         format.gamma_curve = GAMMA_sRGB;
98
99         YCbCrFormat ycbcr_format;
100         ycbcr_format.luma_coefficients = YCBCR_REC_601;
101         ycbcr_format.full_range = true;
102         ycbcr_format.num_levels = 256;
103         ycbcr_format.chroma_subsampling_x = 1;
104         ycbcr_format.chroma_subsampling_y = 1;
105         ycbcr_format.cb_x_position = 0.5f;
106         ycbcr_format.cb_y_position = 0.5f;
107         ycbcr_format.cr_x_position = 0.5f;
108         ycbcr_format.cr_y_position = 0.5f;
109
110         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
111         input->set_pixel_data(0, y);
112         input->set_pixel_data(1, cb);
113         input->set_pixel_data(2, cr);
114         tester.get_chain()->add_input(input);
115
116         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
117
118         // Y'CbCr isn't 100% accurate (the input values are rounded),
119         // so we need some leeway.
120         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
121 }
122
123 TEST(YCbCrInputTest, Rec709) {
124         const int width = 1;
125         const int height = 5;
126
127         // Pure-color test inputs, calculated with the formulas in Rec. 709
128         // page 19, items 3.4 and 3.5.
129         unsigned char y[width * height] = {
130                 16, 235, 63, 173, 32, 
131         };
132         unsigned char cb[width * height] = {
133                 128, 128, 102, 42, 240,
134         };
135         unsigned char cr[width * height] = {
136                 128, 128, 240, 26, 118,
137         };
138         float expected_data[4 * width * height] = {
139                 0.0, 0.0, 0.0, 1.0,
140                 1.0, 1.0, 1.0, 1.0,
141                 1.0, 0.0, 0.0, 1.0,
142                 0.0, 1.0, 0.0, 1.0,
143                 0.0, 0.0, 1.0, 1.0,
144         };
145         float out_data[4 * width * height];
146
147         EffectChainTester tester(NULL, width, height);
148
149         ImageFormat format;
150         format.color_space = COLORSPACE_sRGB;
151         format.gamma_curve = GAMMA_sRGB;
152
153         YCbCrFormat ycbcr_format;
154         ycbcr_format.luma_coefficients = YCBCR_REC_709;
155         ycbcr_format.full_range = false;
156         ycbcr_format.num_levels = 256;
157         ycbcr_format.chroma_subsampling_x = 1;
158         ycbcr_format.chroma_subsampling_y = 1;
159         ycbcr_format.cb_x_position = 0.5f;
160         ycbcr_format.cb_y_position = 0.5f;
161         ycbcr_format.cr_x_position = 0.5f;
162         ycbcr_format.cr_y_position = 0.5f;
163
164         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
165         input->set_pixel_data(0, y);
166         input->set_pixel_data(1, cb);
167         input->set_pixel_data(2, cr);
168         tester.get_chain()->add_input(input);
169
170         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
171
172         // Y'CbCr isn't 100% accurate (the input values are rounded),
173         // so we need some leeway.
174         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
175 }
176
177 TEST(YCbCrInputTest, Rec2020) {
178         const int width = 1;
179         const int height = 5;
180
181         // Pure-color test inputs, calculated with the formulas in Rec. 2020
182         // page 4, tables 4 and 5 (for conventional non-constant luminance).
183         // Note that we still use 8-bit inputs, even though Rec. 2020 is only
184         // defined for 10- and 12-bit.
185         unsigned char y[width * height] = {
186                 16, 235, 74, 164, 29,
187         };
188         unsigned char cb[width * height] = {
189                 128, 128, 97, 47, 240,
190         };
191         unsigned char cr[width * height] = {
192                 128, 128, 240, 25, 119,
193         };
194         float expected_data[4 * width * height] = {
195                 0.0, 0.0, 0.0, 1.0,
196                 1.0, 1.0, 1.0, 1.0,
197                 1.0, 0.0, 0.0, 1.0,
198                 0.0, 1.0, 0.0, 1.0,
199                 0.0, 0.0, 1.0, 1.0,
200         };
201         float out_data[4 * width * height];
202
203         EffectChainTester tester(NULL, width, height);
204
205         ImageFormat format;
206         format.color_space = COLORSPACE_sRGB;
207         format.gamma_curve = GAMMA_sRGB;
208
209         YCbCrFormat ycbcr_format;
210         ycbcr_format.luma_coefficients = YCBCR_REC_2020;
211         ycbcr_format.full_range = false;
212         ycbcr_format.num_levels = 256;
213         ycbcr_format.chroma_subsampling_x = 1;
214         ycbcr_format.chroma_subsampling_y = 1;
215         ycbcr_format.cb_x_position = 0.5f;
216         ycbcr_format.cb_y_position = 0.5f;
217         ycbcr_format.cr_x_position = 0.5f;
218         ycbcr_format.cr_y_position = 0.5f;
219
220         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
221         input->set_pixel_data(0, y);
222         input->set_pixel_data(1, cb);
223         input->set_pixel_data(2, cr);
224         tester.get_chain()->add_input(input);
225
226         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
227
228         // Y'CbCr isn't 100% accurate (the input values are rounded),
229         // so we need some leeway.
230         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
231 }
232
233 TEST(YCbCrInputTest, Subsampling420) {
234         const int width = 4;
235         const int height = 4;
236
237         unsigned char y[width * height] = {
238                 126, 126, 126, 126,
239                 126, 126, 126, 126,
240                 126, 126, 126, 126,
241                 126, 126, 126, 126,
242         };
243         unsigned char cb[(width/2) * (height/2)] = {
244                 64, 128,
245                 128, 192,
246         };
247         unsigned char cr[(width/2) * (height/2)] = {
248                 128, 128,
249                 128, 128,
250         };
251
252         // Note: This is only the blue channel. The chroma samples (with associated
253         // values for blue) are marked off in comments.
254         float expected_data[width * height] = {
255                 0.000, 0.125, 0.375, 0.500, 
256                  /* 0.0 */      /* 0.5 */
257                 0.125, 0.250, 0.500, 0.625,
258
259                 0.375, 0.500, 0.750, 0.875,
260                  /* 0.5 */      /* 1.0 */
261                 0.500, 0.625, 0.875, 1.000,
262         };
263         float out_data[width * height];
264
265         EffectChainTester tester(NULL, width, height);
266
267         ImageFormat format;
268         format.color_space = COLORSPACE_sRGB;
269         format.gamma_curve = GAMMA_sRGB;
270
271         YCbCrFormat ycbcr_format;
272         ycbcr_format.luma_coefficients = YCBCR_REC_601;
273         ycbcr_format.full_range = false;
274         ycbcr_format.num_levels = 256;
275         ycbcr_format.chroma_subsampling_x = 2;
276         ycbcr_format.chroma_subsampling_y = 2;
277         ycbcr_format.cb_x_position = 0.5f;
278         ycbcr_format.cb_y_position = 0.5f;
279         ycbcr_format.cr_x_position = 0.5f;
280         ycbcr_format.cr_y_position = 0.5f;
281
282         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
283         input->set_pixel_data(0, y);
284         input->set_pixel_data(1, cb);
285         input->set_pixel_data(2, cr);
286         tester.get_chain()->add_input(input);
287
288         tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
289
290         // Y'CbCr isn't 100% accurate (the input values are rounded),
291         // so we need some leeway.
292         expect_equal(expected_data, out_data, width, height, 0.01, 0.001);
293 }
294
295 TEST(YCbCrInputTest, Subsampling420WithNonCenteredSamples) {
296         const int width = 4;
297         const int height = 4;
298
299         unsigned char y[width * height] = {
300                 126, 126, 126, 126,
301                 126, 126, 126, 126,
302                 126, 126, 126, 126,
303                 126, 126, 126, 126,
304         };
305         unsigned char cb[(width/2) * (height/2)] = {
306                 64, 128,
307                 128, 192,
308         };
309         unsigned char cr[(width/2) * (height/2)] = {
310                 128, 128,
311                 128, 128,
312         };
313
314         // Note: This is only the blue channel. The chroma samples (with associated
315         // values for blue) are marked off in comments.
316         float expected_data[width * height] = {
317                    0.000, 0.250, 0.500, 0.500, 
318                 /* 0.0 */     /* 0.5 */
319                    0.125, 0.375, 0.625, 0.625,
320
321                    0.375, 0.625, 0.875, 0.875,
322                 /* 0.5 */     /* 1.0 */
323                    0.500, 0.750, 1.000, 1.000,
324         };
325         float out_data[width * height];
326
327         EffectChainTester tester(NULL, width, height);
328
329         ImageFormat format;
330         format.color_space = COLORSPACE_sRGB;
331         format.gamma_curve = GAMMA_sRGB;
332
333         YCbCrFormat ycbcr_format;
334         ycbcr_format.luma_coefficients = YCBCR_REC_601;
335         ycbcr_format.full_range = false;
336         ycbcr_format.num_levels = 256;
337         ycbcr_format.chroma_subsampling_x = 2;
338         ycbcr_format.chroma_subsampling_y = 2;
339         ycbcr_format.cb_x_position = 0.0f;
340         ycbcr_format.cb_y_position = 0.5f;
341         ycbcr_format.cr_x_position = 0.0f;
342         ycbcr_format.cr_y_position = 0.5f;
343
344         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
345         input->set_pixel_data(0, y);
346         input->set_pixel_data(1, cb);
347         input->set_pixel_data(2, cr);
348         tester.get_chain()->add_input(input);
349
350         tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
351
352         // Y'CbCr isn't 100% accurate (the input values are rounded),
353         // so we need some leeway.
354         expect_equal(expected_data, out_data, width, height, 0.01, 0.001);
355 }
356
357 // Yes, some 4:2:2 formats actually have this craziness.
358 TEST(YCbCrInputTest, DifferentCbAndCrPositioning) {
359         const int width = 4;
360         const int height = 4;
361
362         unsigned char y[width * height] = {
363                 126, 126, 126, 126,
364                 126, 126, 126, 126,
365                 126, 126, 126, 126,
366                 126, 126, 126, 126,
367         };
368         unsigned char cb[(width/2) * height] = {
369                 64, 128,
370                 128, 192,
371                 128, 128,
372                 128, 128,
373         };
374         unsigned char cr[(width/2) * height] = {
375                 48, 128,
376                 128, 208,
377                 128, 128,
378                 128, 128,
379         };
380
381         // Chroma samples in this csae are always co-sited with a luma sample;
382         // their associated color values and position are marked off in comments.
383         float expected_data_blue[width * height] = {
384                    0.000 /* 0.0 */, 0.250,           0.500 /* 0.5 */, 0.500, 
385                    0.500 /* 0.5 */, 0.750,           1.000 /* 1.0 */, 1.000, 
386                    0.500 /* 0.5 */, 0.500,           0.500 /* 0.5 */, 0.500, 
387                    0.500 /* 0.5 */, 0.500,           0.500 /* 0.5 */, 0.500, 
388         };
389         float expected_data_red[width * height] = {
390                    0.000,           0.000 /* 0.0 */, 0.250,           0.500 /* 0.5 */, 
391                    0.500,           0.500 /* 0.5 */, 0.750,           1.000 /* 1.0 */, 
392                    0.500,           0.500 /* 0.5 */, 0.500,           0.500 /* 0.5 */, 
393                    0.500,           0.500 /* 0.5 */, 0.500,           0.500 /* 0.5 */, 
394         };
395         float out_data[width * height];
396
397         EffectChainTester tester(NULL, width, height);
398
399         ImageFormat format;
400         format.color_space = COLORSPACE_sRGB;
401         format.gamma_curve = GAMMA_sRGB;
402
403         YCbCrFormat ycbcr_format;
404         ycbcr_format.luma_coefficients = YCBCR_REC_601;
405         ycbcr_format.full_range = false;
406         ycbcr_format.num_levels = 256;
407         ycbcr_format.chroma_subsampling_x = 2;
408         ycbcr_format.chroma_subsampling_y = 1;
409         ycbcr_format.cb_x_position = 0.0f;
410         ycbcr_format.cb_y_position = 0.5f;
411         ycbcr_format.cr_x_position = 1.0f;
412         ycbcr_format.cr_y_position = 0.5f;
413
414         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
415         input->set_pixel_data(0, y);
416         input->set_pixel_data(1, cb);
417         input->set_pixel_data(2, cr);
418         tester.get_chain()->add_input(input);
419
420         // Y'CbCr isn't 100% accurate (the input values are rounded),
421         // so we need some leeway.
422         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
423         expect_equal(expected_data_red, out_data, width, height, 0.02, 0.002);
424
425         tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
426         expect_equal(expected_data_blue, out_data, width, height, 0.01, 0.001);
427 }
428
429 TEST(YCbCrInputTest, PBO) {
430         const int width = 1;
431         const int height = 5;
432
433         // Pure-color test inputs, calculated with the formulas in Rec. 601
434         // section 2.5.4.
435         unsigned char data[width * height * 3] = {
436                 16, 235, 81, 145, 41,
437                 128, 128, 90, 54, 240,
438                 128, 128, 240, 34, 110,
439         };
440         float expected_data[4 * width * height] = {
441                 0.0, 0.0, 0.0, 1.0,
442                 1.0, 1.0, 1.0, 1.0,
443                 1.0, 0.0, 0.0, 1.0,
444                 0.0, 1.0, 0.0, 1.0,
445                 0.0, 0.0, 1.0, 1.0,
446         };
447         float out_data[4 * width * height];
448
449         GLuint pbo;
450         glGenBuffers(1, &pbo);
451         glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo);
452         glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, width * height * 3, data, GL_STREAM_DRAW);
453         glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
454
455         EffectChainTester tester(NULL, width, height);
456
457         ImageFormat format;
458         format.color_space = COLORSPACE_sRGB;
459         format.gamma_curve = GAMMA_sRGB;
460
461         YCbCrFormat ycbcr_format;
462         ycbcr_format.luma_coefficients = YCBCR_REC_601;
463         ycbcr_format.full_range = false;
464         ycbcr_format.num_levels = 256;
465         ycbcr_format.chroma_subsampling_x = 1;
466         ycbcr_format.chroma_subsampling_y = 1;
467         ycbcr_format.cb_x_position = 0.5f;
468         ycbcr_format.cb_y_position = 0.5f;
469         ycbcr_format.cr_x_position = 0.5f;
470         ycbcr_format.cr_y_position = 0.5f;
471
472         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
473         input->set_pixel_data(0, (unsigned char *)BUFFER_OFFSET(0), pbo);
474         input->set_pixel_data(1, (unsigned char *)BUFFER_OFFSET(width * height), pbo);
475         input->set_pixel_data(2, (unsigned char *)BUFFER_OFFSET(width * height * 2), pbo);
476         tester.get_chain()->add_input(input);
477
478         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
479
480         // Y'CbCr isn't 100% accurate (the input values are rounded),
481         // so we need some leeway.
482         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
483
484         glDeleteBuffers(1, &pbo);
485 }
486
487 TEST(YCbCrInputTest, CombinedCbAndCr) {
488         const int width = 1;
489         const int height = 5;
490
491         // Pure-color test inputs, calculated with the formulas in Rec. 601
492         // section 2.5.4.
493         unsigned char y[width * height] = {
494                 16, 235, 81, 145, 41,
495         };
496         unsigned char cb_cr[width * height * 2] = {
497                 128, 128,
498                 128, 128,
499                  90, 240,
500                  54,  34,
501                 240, 110,
502         };
503         float expected_data[4 * width * height] = {
504                 0.0, 0.0, 0.0, 1.0,
505                 1.0, 1.0, 1.0, 1.0,
506                 1.0, 0.0, 0.0, 1.0,
507                 0.0, 1.0, 0.0, 1.0,
508                 0.0, 0.0, 1.0, 1.0,
509         };
510         float out_data[4 * width * height];
511
512         EffectChainTester tester(NULL, width, height);
513
514         ImageFormat format;
515         format.color_space = COLORSPACE_sRGB;
516         format.gamma_curve = GAMMA_sRGB;
517
518         YCbCrFormat ycbcr_format;
519         ycbcr_format.luma_coefficients = YCBCR_REC_601;
520         ycbcr_format.full_range = false;
521         ycbcr_format.num_levels = 256;
522         ycbcr_format.chroma_subsampling_x = 1;
523         ycbcr_format.chroma_subsampling_y = 1;
524         ycbcr_format.cb_x_position = 0.5f;
525         ycbcr_format.cb_y_position = 0.5f;
526         ycbcr_format.cr_x_position = 0.5f;
527         ycbcr_format.cr_y_position = 0.5f;
528
529         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_SPLIT_Y_AND_CBCR);
530         input->set_pixel_data(0, y);
531         input->set_pixel_data(1, cb_cr);
532         tester.get_chain()->add_input(input);
533
534         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
535
536         // Y'CbCr isn't 100% accurate (the input values are rounded),
537         // so we need some leeway.
538         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
539 }
540
541 }  // namespace movit