]> git.sesse.net Git - movit/blob - ycbcr_input_test.cpp
Another round of include-what-you-use.
[movit] / ycbcr_input_test.cpp
1 // Unit tests for YCbCrInput.
2 // FIXME: This class really ought to support mipmaps.
3
4 #include <GL/glew.h>
5 #include <stddef.h>
6
7 #include "effect_chain.h"
8 #include "gtest/gtest.h"
9 #include "test_util.h"
10 #include "util.h"
11 #include "ycbcr_input.h"
12
13 TEST(YCbCrInput, Simple444) {
14         const int width = 1;
15         const int height = 5;
16
17         // Pure-color test inputs, calculated with the formulas in Rec. 601
18         // section 2.5.4.
19         unsigned char y[width * height] = {
20                 16, 235, 81, 145, 41,
21         };
22         unsigned char cb[width * height] = {
23                 128, 128, 90, 54, 240,
24         };
25         unsigned char cr[width * height] = {
26                 128, 128, 240, 34, 110,
27         };
28         float expected_data[4 * width * height] = {
29                 0.0, 0.0, 0.0, 1.0,
30                 1.0, 1.0, 1.0, 1.0,
31                 1.0, 0.0, 0.0, 1.0,
32                 0.0, 1.0, 0.0, 1.0,
33                 0.0, 0.0, 1.0, 1.0,
34         };
35         float out_data[4 * width * height];
36
37         EffectChainTester tester(NULL, width, height);
38
39         ImageFormat format;
40         format.color_space = COLORSPACE_sRGB;
41         format.gamma_curve = GAMMA_sRGB;
42
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;
52
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);
58
59         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
60
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);
64 }
65
66 TEST(YCbCrInput, FullRangeRec601) {
67         const int width = 1;
68         const int height = 5;
69
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] = {
74                 0, 255, 76, 150, 29,
75         };
76         unsigned char cb[width * height] = {
77                 128, 128, 85, 44, 255,
78         };
79         unsigned char cr[width * height] = {
80                 128, 128, 255, 21, 107,
81         };
82         float expected_data[4 * width * height] = {
83                 0.0, 0.0, 0.0, 1.0,
84                 1.0, 1.0, 1.0, 1.0,
85                 1.0, 0.0, 0.0, 1.0,
86                 0.0, 1.0, 0.0, 1.0,
87                 0.0, 0.0, 1.0, 1.0,
88         };
89         float out_data[4 * width * height];
90
91         EffectChainTester tester(NULL, width, height);
92
93         ImageFormat format;
94         format.color_space = COLORSPACE_sRGB;
95         format.gamma_curve = GAMMA_sRGB;
96
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;
106
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);
112
113         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
114
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);
118 }
119
120 TEST(YCbCrInput, Rec709) {
121         const int width = 1;
122         const int height = 5;
123
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, 
128         };
129         unsigned char cb[width * height] = {
130                 128, 128, 102, 42, 240,
131         };
132         unsigned char cr[width * height] = {
133                 128, 128, 240, 26, 118,
134         };
135         float expected_data[4 * width * height] = {
136                 0.0, 0.0, 0.0, 1.0,
137                 1.0, 1.0, 1.0, 1.0,
138                 1.0, 0.0, 0.0, 1.0,
139                 0.0, 1.0, 0.0, 1.0,
140                 0.0, 0.0, 1.0, 1.0,
141         };
142         float out_data[4 * width * height];
143
144         EffectChainTester tester(NULL, width, height);
145
146         ImageFormat format;
147         format.color_space = COLORSPACE_sRGB;
148         format.gamma_curve = GAMMA_sRGB;
149
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;
159
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);
165
166         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
167
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);
171 }
172
173 TEST(YCbCrInput, Rec2020) {
174         const int width = 1;
175         const int height = 5;
176
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,
183         };
184         unsigned char cb[width * height] = {
185                 128, 128, 97, 47, 240,
186         };
187         unsigned char cr[width * height] = {
188                 128, 128, 240, 25, 119,
189         };
190         float expected_data[4 * width * height] = {
191                 0.0, 0.0, 0.0, 1.0,
192                 1.0, 1.0, 1.0, 1.0,
193                 1.0, 0.0, 0.0, 1.0,
194                 0.0, 1.0, 0.0, 1.0,
195                 0.0, 0.0, 1.0, 1.0,
196         };
197         float out_data[4 * width * height];
198
199         EffectChainTester tester(NULL, width, height);
200
201         ImageFormat format;
202         format.color_space = COLORSPACE_sRGB;
203         format.gamma_curve = GAMMA_sRGB;
204
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;
214
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);
220
221         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
222
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);
226 }
227
228 TEST(YCbCrInput, Subsampling420) {
229         const int width = 4;
230         const int height = 4;
231
232         unsigned char y[width * height] = {
233                 126, 126, 126, 126,
234                 126, 126, 126, 126,
235                 126, 126, 126, 126,
236                 126, 126, 126, 126,
237         };
238         unsigned char cb[(width/2) * (height/2)] = {
239                 64, 128,
240                 128, 192,
241         };
242         unsigned char cr[(width/2) * (height/2)] = {
243                 128, 128,
244                 128, 128,
245         };
246
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, 
251                  /* 0.0 */      /* 0.5 */
252                 0.125, 0.250, 0.500, 0.625,
253
254                 0.375, 0.500, 0.750, 0.875,
255                  /* 0.5 */      /* 1.0 */
256                 0.500, 0.625, 0.875, 1.000,
257         };
258         float out_data[width * height];
259
260         EffectChainTester tester(NULL, width, height);
261
262         ImageFormat format;
263         format.color_space = COLORSPACE_sRGB;
264         format.gamma_curve = GAMMA_sRGB;
265
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;
275
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);
281
282         tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
283
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);
287 }
288
289 TEST(YCbCrInput, Subsampling420WithNonCenteredSamples) {
290         const int width = 4;
291         const int height = 4;
292
293         unsigned char y[width * height] = {
294                 126, 126, 126, 126,
295                 126, 126, 126, 126,
296                 126, 126, 126, 126,
297                 126, 126, 126, 126,
298         };
299         unsigned char cb[(width/2) * (height/2)] = {
300                 64, 128,
301                 128, 192,
302         };
303         unsigned char cr[(width/2) * (height/2)] = {
304                 128, 128,
305                 128, 128,
306         };
307
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, 
312                 /* 0.0 */     /* 0.5 */
313                    0.125, 0.375, 0.625, 0.625,
314
315                    0.375, 0.625, 0.875, 0.875,
316                 /* 0.5 */     /* 1.0 */
317                    0.500, 0.750, 1.000, 1.000,
318         };
319         float out_data[width * height];
320
321         EffectChainTester tester(NULL, width, height);
322
323         ImageFormat format;
324         format.color_space = COLORSPACE_sRGB;
325         format.gamma_curve = GAMMA_sRGB;
326
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;
336
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);
342
343         tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
344
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);
348 }
349
350 // Yes, some 4:2:2 formats actually have this craziness.
351 TEST(YCbCrInput, DifferentCbAndCrPositioning) {
352         const int width = 4;
353         const int height = 4;
354
355         unsigned char y[width * height] = {
356                 126, 126, 126, 126,
357                 126, 126, 126, 126,
358                 126, 126, 126, 126,
359                 126, 126, 126, 126,
360         };
361         unsigned char cb[(width/2) * height] = {
362                 64, 128,
363                 128, 192,
364                 128, 128,
365                 128, 128,
366         };
367         unsigned char cr[(width/2) * height] = {
368                 48, 128,
369                 128, 208,
370                 128, 128,
371                 128, 128,
372         };
373
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, 
381         };
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 */, 
387         };
388         float out_data[width * height];
389
390         EffectChainTester tester(NULL, width, height);
391
392         ImageFormat format;
393         format.color_space = COLORSPACE_sRGB;
394         format.gamma_curve = GAMMA_sRGB;
395
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;
405
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);
411
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);
416
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);
419 }
420
421 TEST(YCbCrInput, PBO) {
422         const int width = 1;
423         const int height = 5;
424
425         // Pure-color test inputs, calculated with the formulas in Rec. 601
426         // section 2.5.4.
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,
431         };
432         float expected_data[4 * width * height] = {
433                 0.0, 0.0, 0.0, 1.0,
434                 1.0, 1.0, 1.0, 1.0,
435                 1.0, 0.0, 0.0, 1.0,
436                 0.0, 1.0, 0.0, 1.0,
437                 0.0, 0.0, 1.0, 1.0,
438         };
439         float out_data[4 * width * height];
440
441         GLuint pbo;
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);
446
447         EffectChainTester tester(NULL, width, height);
448
449         ImageFormat format;
450         format.color_space = COLORSPACE_sRGB;
451         format.gamma_curve = GAMMA_sRGB;
452
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;
462
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);
468
469         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
470
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);
474
475         glDeleteBuffers(1, &pbo);
476 }