]> git.sesse.net Git - movit/blob - ycbcr_input_test.cpp
Loosen up some restrictions on strong one-to-one-effects.
[movit] / ycbcr_input_test.cpp
1 // Unit tests for YCbCrInput. Also tests the matrix functions in ycbcr.cpp directly.
2
3 #include <epoxy/gl.h>
4 #include <stddef.h>
5
6 #include <Eigen/Core>
7 #include <Eigen/LU>
8
9 #include "effect_chain.h"
10 #include "gtest/gtest.h"
11 #include "test_util.h"
12 #include "util.h"
13 #include "resource_pool.h"
14 #include "ycbcr_input.h"
15
16 using namespace std;
17
18 namespace movit {
19
20 TEST(YCbCrInputTest, Simple444) {
21         const int width = 1;
22         const int height = 5;
23
24         // Pure-color test inputs, calculated with the formulas in Rec. 601
25         // section 2.5.4.
26         unsigned char y[width * height] = {
27                 16, 235, 81, 145, 41,
28         };
29         unsigned char cb[width * height] = {
30                 128, 128, 90, 54, 240,
31         };
32         unsigned char cr[width * height] = {
33                 128, 128, 240, 34, 110,
34         };
35         float expected_data[4 * width * height] = {
36                 0.0, 0.0, 0.0, 1.0,
37                 1.0, 1.0, 1.0, 1.0,
38                 1.0, 0.0, 0.0, 1.0,
39                 0.0, 1.0, 0.0, 1.0,
40                 0.0, 0.0, 1.0, 1.0,
41         };
42         float out_data[4 * width * height];
43
44         EffectChainTester tester(nullptr, width, height);
45
46         ImageFormat format;
47         format.color_space = COLORSPACE_sRGB;
48         format.gamma_curve = GAMMA_sRGB;
49
50         YCbCrFormat ycbcr_format;
51         ycbcr_format.luma_coefficients = YCBCR_REC_601;
52         ycbcr_format.full_range = false;
53         ycbcr_format.num_levels = 256;
54         ycbcr_format.chroma_subsampling_x = 1;
55         ycbcr_format.chroma_subsampling_y = 1;
56         ycbcr_format.cb_x_position = 0.5f;
57         ycbcr_format.cb_y_position = 0.5f;
58         ycbcr_format.cr_x_position = 0.5f;
59         ycbcr_format.cr_y_position = 0.5f;
60
61         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
62         input->set_pixel_data(0, y);
63         input->set_pixel_data(1, cb);
64         input->set_pixel_data(2, cr);
65         tester.get_chain()->add_input(input);
66
67         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
68
69         // Y'CbCr isn't 100% accurate (the input values are rounded),
70         // so we need some leeway.
71         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
72 }
73
74 TEST(YCbCrInputTest, Interleaved444) {
75         const int width = 1;
76         const int height = 5;
77
78         // Same data as Simple444, just rearranged.
79         unsigned char data[width * height * 3] = {
80                  16, 128, 128,
81                 235, 128, 128,
82                  81,  90, 240,
83                 145,  54,  34,
84                  41, 240, 110,
85         };
86         float expected_data[4 * width * height] = {
87                 0.0, 0.0, 0.0, 1.0,
88                 1.0, 1.0, 1.0, 1.0,
89                 1.0, 0.0, 0.0, 1.0,
90                 0.0, 1.0, 0.0, 1.0,
91                 0.0, 0.0, 1.0, 1.0,
92         };
93         float out_data[4 * width * height];
94
95         EffectChainTester tester(nullptr, width, height);
96
97         ImageFormat format;
98         format.color_space = COLORSPACE_sRGB;
99         format.gamma_curve = GAMMA_sRGB;
100
101         YCbCrFormat ycbcr_format;
102         ycbcr_format.luma_coefficients = YCBCR_REC_601;
103         ycbcr_format.full_range = false;
104         ycbcr_format.num_levels = 256;
105         ycbcr_format.chroma_subsampling_x = 1;
106         ycbcr_format.chroma_subsampling_y = 1;
107         ycbcr_format.cb_x_position = 0.5f;
108         ycbcr_format.cb_y_position = 0.5f;
109         ycbcr_format.cr_x_position = 0.5f;
110         ycbcr_format.cr_y_position = 0.5f;
111
112         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_INTERLEAVED);
113         input->set_pixel_data(0, data);
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, FullRangeRec601) {
124         const int width = 1;
125         const int height = 5;
126
127         // Pure-color test inputs, calculated with the formulas in Rec. 601
128         // section 2.5.4 but without the scaling factors applied
129         // (so both R, G, B, Y, Cb and R vary from 0 to 255).
130         unsigned char y[width * height] = {
131                 0, 255, 76, 150, 29,
132         };
133         unsigned char cb[width * height] = {
134                 128, 128, 85, 44, 255,
135         };
136         unsigned char cr[width * height] = {
137                 128, 128, 255, 21, 107,
138         };
139         float expected_data[4 * width * height] = {
140                 0.0, 0.0, 0.0, 1.0,
141                 1.0, 1.0, 1.0, 1.0,
142                 1.0, 0.0, 0.0, 1.0,
143                 0.0, 1.0, 0.0, 1.0,
144                 0.0, 0.0, 1.0, 1.0,
145         };
146         float out_data[4 * width * height];
147
148         EffectChainTester tester(nullptr, width, height);
149
150         ImageFormat format;
151         format.color_space = COLORSPACE_sRGB;
152         format.gamma_curve = GAMMA_sRGB;
153
154         YCbCrFormat ycbcr_format;
155         ycbcr_format.luma_coefficients = YCBCR_REC_601;
156         ycbcr_format.full_range = true;
157         ycbcr_format.num_levels = 256;
158         ycbcr_format.chroma_subsampling_x = 1;
159         ycbcr_format.chroma_subsampling_y = 1;
160         ycbcr_format.cb_x_position = 0.5f;
161         ycbcr_format.cb_y_position = 0.5f;
162         ycbcr_format.cr_x_position = 0.5f;
163         ycbcr_format.cr_y_position = 0.5f;
164
165         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
166         input->set_pixel_data(0, y);
167         input->set_pixel_data(1, cb);
168         input->set_pixel_data(2, cr);
169         tester.get_chain()->add_input(input);
170
171         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
172
173         // Y'CbCr isn't 100% accurate (the input values are rounded),
174         // so we need some leeway.
175         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
176 }
177
178 TEST(YCbCrInputTest, Rec709) {
179         const int width = 1;
180         const int height = 5;
181
182         // Pure-color test inputs, calculated with the formulas in Rec. 709
183         // page 19, items 3.4 and 3.5.
184         unsigned char y[width * height] = {
185                 16, 235, 63, 173, 32, 
186         };
187         unsigned char cb[width * height] = {
188                 128, 128, 102, 42, 240,
189         };
190         unsigned char cr[width * height] = {
191                 128, 128, 240, 26, 118,
192         };
193         float expected_data[4 * width * height] = {
194                 0.0, 0.0, 0.0, 1.0,
195                 1.0, 1.0, 1.0, 1.0,
196                 1.0, 0.0, 0.0, 1.0,
197                 0.0, 1.0, 0.0, 1.0,
198                 0.0, 0.0, 1.0, 1.0,
199         };
200         float out_data[4 * width * height];
201
202         EffectChainTester tester(nullptr, width, height);
203
204         ImageFormat format;
205         format.color_space = COLORSPACE_sRGB;
206         format.gamma_curve = GAMMA_sRGB;
207
208         YCbCrFormat ycbcr_format;
209         ycbcr_format.luma_coefficients = YCBCR_REC_709;
210         ycbcr_format.full_range = false;
211         ycbcr_format.num_levels = 256;
212         ycbcr_format.chroma_subsampling_x = 1;
213         ycbcr_format.chroma_subsampling_y = 1;
214         ycbcr_format.cb_x_position = 0.5f;
215         ycbcr_format.cb_y_position = 0.5f;
216         ycbcr_format.cr_x_position = 0.5f;
217         ycbcr_format.cr_y_position = 0.5f;
218
219         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
220         input->set_pixel_data(0, y);
221         input->set_pixel_data(1, cb);
222         input->set_pixel_data(2, cr);
223         tester.get_chain()->add_input(input);
224
225         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
226
227         // Y'CbCr isn't 100% accurate (the input values are rounded),
228         // so we need some leeway.
229         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
230 }
231
232 TEST(YCbCrInputTest, Rec2020) {
233         const int width = 1;
234         const int height = 5;
235
236         // Pure-color test inputs, calculated with the formulas in Rec. 2020
237         // page 4, tables 4 and 5 (for conventional non-constant luminance).
238         // Note that we still use 8-bit inputs, even though Rec. 2020 is only
239         // defined for 10- and 12-bit.
240         unsigned char y[width * height] = {
241                 16, 235, 74, 164, 29,
242         };
243         unsigned char cb[width * height] = {
244                 128, 128, 97, 47, 240,
245         };
246         unsigned char cr[width * height] = {
247                 128, 128, 240, 25, 119,
248         };
249         float expected_data[4 * width * height] = {
250                 0.0, 0.0, 0.0, 1.0,
251                 1.0, 1.0, 1.0, 1.0,
252                 1.0, 0.0, 0.0, 1.0,
253                 0.0, 1.0, 0.0, 1.0,
254                 0.0, 0.0, 1.0, 1.0,
255         };
256         float out_data[4 * width * height];
257
258         EffectChainTester tester(nullptr, width, height);
259
260         ImageFormat format;
261         format.color_space = COLORSPACE_sRGB;
262         format.gamma_curve = GAMMA_sRGB;
263
264         YCbCrFormat ycbcr_format;
265         ycbcr_format.luma_coefficients = YCBCR_REC_2020;
266         ycbcr_format.full_range = false;
267         ycbcr_format.num_levels = 256;
268         ycbcr_format.chroma_subsampling_x = 1;
269         ycbcr_format.chroma_subsampling_y = 1;
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;
274
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);
280
281         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
282
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, 4 * width, height, 0.025, 0.002);
286 }
287
288 // Very similar to Rec709.
289 TEST(YCbCrInputTest, ChangeFormat) {
290         const int width = 1;
291         const int height = 5;
292
293         // Pure-color test inputs, calculated with the formulas in Rec. 709
294         // page 19, items 3.4 and 3.5.
295         unsigned char y[width * height] = {
296                 16, 235, 63, 173, 32,
297         };
298         unsigned char cb[width * height] = {
299                 128, 128, 102, 42, 240,
300         };
301         unsigned char cr[width * height] = {
302                 128, 128, 240, 26, 118,
303         };
304         float expected_data[4 * width * height] = {
305                 0.0, 0.0, 0.0, 1.0,
306                 1.0, 1.0, 1.0, 1.0,
307                 1.0, 0.0, 0.0, 1.0,
308                 0.0, 1.0, 0.0, 1.0,
309                 0.0, 0.0, 1.0, 1.0,
310         };
311         float out_data[4 * width * height];
312
313         EffectChainTester tester(nullptr, width, height);
314
315         ImageFormat format;
316         format.color_space = COLORSPACE_sRGB;
317         format.gamma_curve = GAMMA_sRGB;
318
319         // Basically all of these values will be changed after finalize.
320         YCbCrFormat initial_ycbcr_format;
321         initial_ycbcr_format.luma_coefficients = YCBCR_REC_601;
322         initial_ycbcr_format.full_range = true;
323         initial_ycbcr_format.num_levels = 1024;
324         initial_ycbcr_format.chroma_subsampling_x = 1;
325         initial_ycbcr_format.chroma_subsampling_y = 5;
326         initial_ycbcr_format.cb_x_position = 0.0f;
327         initial_ycbcr_format.cb_y_position = 0.5f;
328         initial_ycbcr_format.cr_x_position = 0.0f;
329         initial_ycbcr_format.cr_y_position = 0.5f;
330
331         YCbCrInput *input = new YCbCrInput(format, initial_ycbcr_format, width, height);
332         input->set_pixel_data(0, y);
333         input->set_pixel_data(1, cb);
334         input->set_pixel_data(2, cr);
335         tester.get_chain()->add_input(input);
336
337         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
338
339         // Rerun with the right format.
340         YCbCrFormat ycbcr_format;
341         ycbcr_format.luma_coefficients = YCBCR_REC_709;
342         ycbcr_format.full_range = false;
343         ycbcr_format.num_levels = 256;
344         ycbcr_format.chroma_subsampling_x = 1;
345         ycbcr_format.chroma_subsampling_y = 1;
346         ycbcr_format.cb_x_position = 0.5f;
347         ycbcr_format.cb_y_position = 0.5f;
348         ycbcr_format.cr_x_position = 0.5f;
349         ycbcr_format.cr_y_position = 0.5f;
350
351         input->change_ycbcr_format(ycbcr_format);
352         input->set_width(width);
353         input->set_height(height);
354
355         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
356
357         // Y'CbCr isn't 100% accurate (the input values are rounded),
358         // so we need some leeway.
359         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
360 }
361
362 TEST(YCbCrInputTest, Subsampling420) {
363         const int width = 4;
364         const int height = 4;
365
366         unsigned char y[width * height] = {
367                 126, 126, 126, 126,
368                 126, 126, 126, 126,
369                 126, 126, 126, 126,
370                 126, 126, 126, 126,
371         };
372         unsigned char cb[(width/2) * (height/2)] = {
373                 64, 128,
374                 128, 192,
375         };
376         unsigned char cr[(width/2) * (height/2)] = {
377                 128, 128,
378                 128, 128,
379         };
380
381         // Note: This is only the blue channel. The chroma samples (with associated
382         // values for blue) are marked off in comments.
383         float expected_data[width * height] = {
384                 0.000, 0.125, 0.375, 0.500, 
385                  /* 0.0 */      /* 0.5 */
386                 0.125, 0.250, 0.500, 0.625,
387
388                 0.375, 0.500, 0.750, 0.875,
389                  /* 0.5 */      /* 1.0 */
390                 0.500, 0.625, 0.875, 1.000,
391         };
392         float out_data[width * height];
393
394         EffectChainTester tester(nullptr, width, height);
395
396         ImageFormat format;
397         format.color_space = COLORSPACE_sRGB;
398         format.gamma_curve = GAMMA_sRGB;
399
400         YCbCrFormat ycbcr_format;
401         ycbcr_format.luma_coefficients = YCBCR_REC_601;
402         ycbcr_format.full_range = false;
403         ycbcr_format.num_levels = 256;
404         ycbcr_format.chroma_subsampling_x = 2;
405         ycbcr_format.chroma_subsampling_y = 2;
406         ycbcr_format.cb_x_position = 0.5f;
407         ycbcr_format.cb_y_position = 0.5f;
408         ycbcr_format.cr_x_position = 0.5f;
409         ycbcr_format.cr_y_position = 0.5f;
410
411         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
412         input->set_pixel_data(0, y);
413         input->set_pixel_data(1, cb);
414         input->set_pixel_data(2, cr);
415         tester.get_chain()->add_input(input);
416
417         tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
418
419         // Y'CbCr isn't 100% accurate (the input values are rounded),
420         // so we need some leeway.
421         expect_equal(expected_data, out_data, width, height, 0.01, 0.001);
422 }
423
424 TEST(YCbCrInputTest, Subsampling420WithNonCenteredSamples) {
425         const int width = 4;
426         const int height = 4;
427
428         unsigned char y[width * height] = {
429                 126, 126, 126, 126,
430                 126, 126, 126, 126,
431                 126, 126, 126, 126,
432                 126, 126, 126, 126,
433         };
434         unsigned char cb[(width/2) * (height/2)] = {
435                 64, 128,
436                 128, 192,
437         };
438         unsigned char cr[(width/2) * (height/2)] = {
439                 128, 128,
440                 128, 128,
441         };
442
443         // Note: This is only the blue channel. The chroma samples (with associated
444         // values for blue) are marked off in comments.
445         float expected_data[width * height] = {
446                    0.000, 0.250, 0.500, 0.500, 
447                 /* 0.0 */     /* 0.5 */
448                    0.125, 0.375, 0.625, 0.625,
449
450                    0.375, 0.625, 0.875, 0.875,
451                 /* 0.5 */     /* 1.0 */
452                    0.500, 0.750, 1.000, 1.000,
453         };
454         float out_data[width * height];
455
456         EffectChainTester tester(nullptr, width, height);
457
458         ImageFormat format;
459         format.color_space = COLORSPACE_sRGB;
460         format.gamma_curve = GAMMA_sRGB;
461
462         YCbCrFormat ycbcr_format;
463         ycbcr_format.luma_coefficients = YCBCR_REC_601;
464         ycbcr_format.full_range = false;
465         ycbcr_format.num_levels = 256;
466         ycbcr_format.chroma_subsampling_x = 2;
467         ycbcr_format.chroma_subsampling_y = 2;
468         ycbcr_format.cb_x_position = 0.0f;
469         ycbcr_format.cb_y_position = 0.5f;
470         ycbcr_format.cr_x_position = 0.0f;
471         ycbcr_format.cr_y_position = 0.5f;
472
473         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
474         input->set_pixel_data(0, y);
475         input->set_pixel_data(1, cb);
476         input->set_pixel_data(2, cr);
477         tester.get_chain()->add_input(input);
478
479         tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
480
481         // Y'CbCr isn't 100% accurate (the input values are rounded),
482         // so we need some leeway.
483         expect_equal(expected_data, out_data, width, height, 0.01, 0.0012);
484 }
485
486 // Yes, some 4:2:2 formats actually have this craziness.
487 TEST(YCbCrInputTest, DifferentCbAndCrPositioning) {
488         const int width = 4;
489         const int height = 4;
490
491         unsigned char y[width * height] = {
492                 126, 126, 126, 126,
493                 126, 126, 126, 126,
494                 126, 126, 126, 126,
495                 126, 126, 126, 126,
496         };
497         unsigned char cb[(width/2) * height] = {
498                 64, 128,
499                 128, 192,
500                 128, 128,
501                 128, 128,
502         };
503         unsigned char cr[(width/2) * height] = {
504                 48, 128,
505                 128, 208,
506                 128, 128,
507                 128, 128,
508         };
509
510         // Chroma samples in this csae are always co-sited with a luma sample;
511         // their associated color values and position are marked off in comments.
512         float expected_data_blue[width * height] = {
513                    0.000 /* 0.0 */, 0.250,           0.500 /* 0.5 */, 0.500, 
514                    0.500 /* 0.5 */, 0.750,           1.000 /* 1.0 */, 1.000, 
515                    0.500 /* 0.5 */, 0.500,           0.500 /* 0.5 */, 0.500, 
516                    0.500 /* 0.5 */, 0.500,           0.500 /* 0.5 */, 0.500, 
517         };
518         float expected_data_red[width * height] = {
519                    0.000,           0.000 /* 0.0 */, 0.250,           0.500 /* 0.5 */, 
520                    0.500,           0.500 /* 0.5 */, 0.750,           1.000 /* 1.0 */, 
521                    0.500,           0.500 /* 0.5 */, 0.500,           0.500 /* 0.5 */, 
522                    0.500,           0.500 /* 0.5 */, 0.500,           0.500 /* 0.5 */, 
523         };
524         float out_data[width * height];
525
526         EffectChainTester tester(nullptr, width, height);
527
528         ImageFormat format;
529         format.color_space = COLORSPACE_sRGB;
530         format.gamma_curve = GAMMA_sRGB;
531
532         YCbCrFormat ycbcr_format;
533         ycbcr_format.luma_coefficients = YCBCR_REC_601;
534         ycbcr_format.full_range = false;
535         ycbcr_format.num_levels = 256;
536         ycbcr_format.chroma_subsampling_x = 2;
537         ycbcr_format.chroma_subsampling_y = 1;
538         ycbcr_format.cb_x_position = 0.0f;
539         ycbcr_format.cb_y_position = 0.5f;
540         ycbcr_format.cr_x_position = 1.0f;
541         ycbcr_format.cr_y_position = 0.5f;
542
543         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
544         input->set_pixel_data(0, y);
545         input->set_pixel_data(1, cb);
546         input->set_pixel_data(2, cr);
547         tester.get_chain()->add_input(input);
548
549         // Y'CbCr isn't 100% accurate (the input values are rounded),
550         // so we need some leeway.
551         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
552         expect_equal(expected_data_red, out_data, width, height, 0.02, 0.002);
553
554         tester.run(out_data, GL_BLUE, COLORSPACE_sRGB, GAMMA_sRGB);
555         expect_equal(expected_data_blue, out_data, width, height, 0.01, 0.001);
556 }
557
558 TEST(YCbCrInputTest, PBO) {
559         const int width = 1;
560         const int height = 5;
561
562         // Pure-color test inputs, calculated with the formulas in Rec. 601
563         // section 2.5.4.
564         unsigned char data[width * height * 3] = {
565                 16, 235, 81, 145, 41,
566                 128, 128, 90, 54, 240,
567                 128, 128, 240, 34, 110,
568         };
569         float expected_data[4 * width * height] = {
570                 0.0, 0.0, 0.0, 1.0,
571                 1.0, 1.0, 1.0, 1.0,
572                 1.0, 0.0, 0.0, 1.0,
573                 0.0, 1.0, 0.0, 1.0,
574                 0.0, 0.0, 1.0, 1.0,
575         };
576         float out_data[4 * width * height];
577
578         GLuint pbo;
579         glGenBuffers(1, &pbo);
580         glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo);
581         glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, width * height * 3, data, GL_STREAM_DRAW);
582         glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
583
584         EffectChainTester tester(nullptr, width, height);
585
586         ImageFormat format;
587         format.color_space = COLORSPACE_sRGB;
588         format.gamma_curve = GAMMA_sRGB;
589
590         YCbCrFormat ycbcr_format;
591         ycbcr_format.luma_coefficients = YCBCR_REC_601;
592         ycbcr_format.full_range = false;
593         ycbcr_format.num_levels = 256;
594         ycbcr_format.chroma_subsampling_x = 1;
595         ycbcr_format.chroma_subsampling_y = 1;
596         ycbcr_format.cb_x_position = 0.5f;
597         ycbcr_format.cb_y_position = 0.5f;
598         ycbcr_format.cr_x_position = 0.5f;
599         ycbcr_format.cr_y_position = 0.5f;
600
601         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
602         input->set_pixel_data(0, (unsigned char *)BUFFER_OFFSET(0), pbo);
603         input->set_pixel_data(1, (unsigned char *)BUFFER_OFFSET(width * height), pbo);
604         input->set_pixel_data(2, (unsigned char *)BUFFER_OFFSET(width * height * 2), pbo);
605         tester.get_chain()->add_input(input);
606
607         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
608
609         // Y'CbCr isn't 100% accurate (the input values are rounded),
610         // so we need some leeway.
611         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
612
613         glDeleteBuffers(1, &pbo);
614 }
615
616 TEST(YCbCrInputTest, CombinedCbAndCr) {
617         const int width = 1;
618         const int height = 5;
619
620         // Pure-color test inputs, calculated with the formulas in Rec. 601
621         // section 2.5.4.
622         unsigned char y[width * height] = {
623                 16, 235, 81, 145, 41,
624         };
625         unsigned char cb_cr[width * height * 2] = {
626                 128, 128,
627                 128, 128,
628                  90, 240,
629                  54,  34,
630                 240, 110,
631         };
632         float expected_data[4 * width * height] = {
633                 0.0, 0.0, 0.0, 1.0,
634                 1.0, 1.0, 1.0, 1.0,
635                 1.0, 0.0, 0.0, 1.0,
636                 0.0, 1.0, 0.0, 1.0,
637                 0.0, 0.0, 1.0, 1.0,
638         };
639         float out_data[4 * width * height];
640
641         EffectChainTester tester(nullptr, width, height);
642
643         ImageFormat format;
644         format.color_space = COLORSPACE_sRGB;
645         format.gamma_curve = GAMMA_sRGB;
646
647         YCbCrFormat ycbcr_format;
648         ycbcr_format.luma_coefficients = YCBCR_REC_601;
649         ycbcr_format.full_range = false;
650         ycbcr_format.num_levels = 256;
651         ycbcr_format.chroma_subsampling_x = 1;
652         ycbcr_format.chroma_subsampling_y = 1;
653         ycbcr_format.cb_x_position = 0.5f;
654         ycbcr_format.cb_y_position = 0.5f;
655         ycbcr_format.cr_x_position = 0.5f;
656         ycbcr_format.cr_y_position = 0.5f;
657
658         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_SPLIT_Y_AND_CBCR);
659         input->set_pixel_data(0, y);
660         input->set_pixel_data(1, cb_cr);
661         tester.get_chain()->add_input(input);
662
663         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
664
665         // Y'CbCr isn't 100% accurate (the input values are rounded),
666         // so we need some leeway.
667         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
668 }
669
670 TEST(YCbCrInputTest, ExternalTexture) {
671         const int width = 1;
672         const int height = 5;
673
674         // Pure-color test inputs, calculated with the formulas in Rec. 601
675         // section 2.5.4.
676         unsigned char y[width * height] = {
677                 16, 235, 81, 145, 41,
678         };
679         unsigned char cb[width * height] = {
680                 128, 128, 90, 54, 240,
681         };
682         unsigned char cr[width * height] = {
683                 128, 128, 240, 34, 110,
684         };
685         float expected_data[4 * width * height] = {
686                 0.0, 0.0, 0.0, 1.0,
687                 1.0, 1.0, 1.0, 1.0,
688                 1.0, 0.0, 0.0, 1.0,
689                 0.0, 1.0, 0.0, 1.0,
690                 0.0, 0.0, 1.0, 1.0,
691         };
692         float out_data[4 * width * height];
693
694         EffectChainTester tester(nullptr, width, height);
695
696         ImageFormat format;
697         format.color_space = COLORSPACE_sRGB;
698         format.gamma_curve = GAMMA_sRGB;
699
700         YCbCrFormat ycbcr_format;
701         ycbcr_format.luma_coefficients = YCBCR_REC_601;
702         ycbcr_format.full_range = false;
703         ycbcr_format.num_levels = 256;
704         ycbcr_format.chroma_subsampling_x = 1;
705         ycbcr_format.chroma_subsampling_y = 1;
706         ycbcr_format.cb_x_position = 0.5f;
707         ycbcr_format.cb_y_position = 0.5f;
708         ycbcr_format.cr_x_position = 0.5f;
709         ycbcr_format.cr_y_position = 0.5f;
710
711         // Make a texture for the Cb data; keep the others as regular uploads.
712         ResourcePool pool;
713         GLuint cb_tex = pool.create_2d_texture(GL_R8, width, height);
714         check_error();
715         glBindTexture(GL_TEXTURE_2D, cb_tex);
716         check_error();
717         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
718         check_error();
719         glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
720         check_error();
721         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, cb);
722         check_error();
723         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
724         check_error();
725         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
726         check_error();
727
728         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
729         input->set_pixel_data(0, y);
730         input->set_texture_num(1, cb_tex);
731         input->set_pixel_data(2, cr);
732         tester.get_chain()->add_input(input);
733
734         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
735
736         pool.release_2d_texture(cb_tex);
737
738         // Y'CbCr isn't 100% accurate (the input values are rounded),
739         // so we need some leeway.
740         expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
741 }
742
743 TEST(YCbCrTest, WikipediaRec601ForwardMatrix) {
744         YCbCrFormat ycbcr_format;
745         ycbcr_format.luma_coefficients = YCBCR_REC_601;
746         ycbcr_format.full_range = false;
747         ycbcr_format.num_levels = 256;
748
749         float offset[3];
750         Eigen::Matrix3d ycbcr_to_rgb;
751         compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
752
753         Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse() * 255.0;
754
755         // Values from https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion.
756         EXPECT_NEAR(  65.481, rgb_to_ycbcr(0,0), 1e-3);
757         EXPECT_NEAR( 128.553, rgb_to_ycbcr(0,1), 1e-3);
758         EXPECT_NEAR(  24.966, rgb_to_ycbcr(0,2), 1e-3);
759
760         EXPECT_NEAR( -37.797, rgb_to_ycbcr(1,0), 1e-3);
761         EXPECT_NEAR( -74.203, rgb_to_ycbcr(1,1), 1e-3);
762         EXPECT_NEAR( 112.000, rgb_to_ycbcr(1,2), 1e-3);
763
764         EXPECT_NEAR( 112.000, rgb_to_ycbcr(2,0), 1e-3);
765         EXPECT_NEAR( -93.786, rgb_to_ycbcr(2,1), 1e-3);
766         EXPECT_NEAR( -18.214, rgb_to_ycbcr(2,2), 1e-3);
767
768         EXPECT_NEAR( 16.0, offset[0] * 255.0, 1e-3);
769         EXPECT_NEAR(128.0, offset[1] * 255.0, 1e-3);
770         EXPECT_NEAR(128.0, offset[2] * 255.0, 1e-3);
771 }
772
773 TEST(YCbCrTest, WikipediaJPEGMatrices) {
774         YCbCrFormat ycbcr_format;
775         ycbcr_format.luma_coefficients = YCBCR_REC_601;
776         ycbcr_format.full_range = true;
777         ycbcr_format.num_levels = 256;
778
779         float offset[3];
780         Eigen::Matrix3d ycbcr_to_rgb;
781         compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
782
783         // Values from https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion.
784         EXPECT_NEAR( 1.00000, ycbcr_to_rgb(0,0), 1e-5);
785         EXPECT_NEAR( 0.00000, ycbcr_to_rgb(0,1), 1e-5);
786         EXPECT_NEAR( 1.40200, ycbcr_to_rgb(0,2), 1e-5);
787
788         EXPECT_NEAR( 1.00000, ycbcr_to_rgb(1,0), 1e-5);
789         EXPECT_NEAR(-0.34414, ycbcr_to_rgb(1,1), 1e-5);
790         EXPECT_NEAR(-0.71414, ycbcr_to_rgb(1,2), 1e-5);
791
792         EXPECT_NEAR( 1.00000, ycbcr_to_rgb(2,0), 1e-5);
793         EXPECT_NEAR( 1.77200, ycbcr_to_rgb(2,1), 1e-5);
794         EXPECT_NEAR( 0.00000, ycbcr_to_rgb(2,2), 1e-5);
795
796         Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse();
797
798         // Same.
799         EXPECT_NEAR( 0.299000, rgb_to_ycbcr(0,0), 1e-6);
800         EXPECT_NEAR( 0.587000, rgb_to_ycbcr(0,1), 1e-6);
801         EXPECT_NEAR( 0.114000, rgb_to_ycbcr(0,2), 1e-6);
802
803         EXPECT_NEAR(-0.168736, rgb_to_ycbcr(1,0), 1e-6);
804         EXPECT_NEAR(-0.331264, rgb_to_ycbcr(1,1), 1e-6);
805         EXPECT_NEAR( 0.500000, rgb_to_ycbcr(1,2), 1e-6);
806
807         EXPECT_NEAR( 0.500000, rgb_to_ycbcr(2,0), 1e-6);
808         EXPECT_NEAR(-0.418688, rgb_to_ycbcr(2,1), 1e-6);
809         EXPECT_NEAR(-0.081312, rgb_to_ycbcr(2,2), 1e-6);
810
811         EXPECT_NEAR(  0.0, offset[0] * 255.0, 1e-3);
812         EXPECT_NEAR(128.0, offset[1] * 255.0, 1e-3);
813         EXPECT_NEAR(128.0, offset[2] * 255.0, 1e-3);
814 }
815
816 TEST(YCbCrTest, BlackmagicForwardMatrix) {
817         YCbCrFormat ycbcr_format;
818         ycbcr_format.luma_coefficients = YCBCR_REC_709;
819         ycbcr_format.full_range = false;
820         ycbcr_format.num_levels = 256;
821
822         float offset[3];
823         Eigen::Matrix3d ycbcr_to_rgb;
824         compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
825
826         Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse();
827
828         // Values from DeckLink SDK documentation.
829         EXPECT_NEAR( 0.183, rgb_to_ycbcr(0,0), 1e-3);
830         EXPECT_NEAR( 0.614, rgb_to_ycbcr(0,1), 1e-3);
831         EXPECT_NEAR( 0.062, rgb_to_ycbcr(0,2), 1e-3);
832
833         EXPECT_NEAR(-0.101, rgb_to_ycbcr(1,0), 1e-3);
834         EXPECT_NEAR(-0.338, rgb_to_ycbcr(1,1), 1e-3);
835         EXPECT_NEAR( 0.439, rgb_to_ycbcr(1,2), 1e-3);
836
837         EXPECT_NEAR( 0.439, rgb_to_ycbcr(2,0), 1e-3);
838         EXPECT_NEAR(-0.399, rgb_to_ycbcr(2,1), 1e-3);
839         EXPECT_NEAR(-0.040, rgb_to_ycbcr(2,2), 1e-3);
840
841         EXPECT_NEAR( 16.0, offset[0] * 255.0, 1e-3);
842         EXPECT_NEAR(128.0, offset[1] * 255.0, 1e-3);
843         EXPECT_NEAR(128.0, offset[2] * 255.0, 1e-3);
844 }
845
846 TEST(YCbCrInputTest, NoData) {
847         const int width = 1;
848         const int height = 5;
849
850         float out_data[4 * width * height];
851
852         EffectChainTester tester(nullptr, width, height);
853
854         ImageFormat format;
855         format.color_space = COLORSPACE_sRGB;
856         format.gamma_curve = GAMMA_sRGB;
857
858         YCbCrFormat ycbcr_format;
859         ycbcr_format.luma_coefficients = YCBCR_REC_601;
860         ycbcr_format.full_range = false;
861         ycbcr_format.num_levels = 256;
862         ycbcr_format.chroma_subsampling_x = 1;
863         ycbcr_format.chroma_subsampling_y = 1;
864         ycbcr_format.cb_x_position = 0.5f;
865         ycbcr_format.cb_y_position = 0.5f;
866         ycbcr_format.cr_x_position = 0.5f;
867         ycbcr_format.cr_y_position = 0.5f;
868
869         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height);
870         tester.get_chain()->add_input(input);
871
872         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
873
874         // Don't care what the output was, just that it does not crash.
875 }
876
877 TEST(YCbCrInputTest, TenBitInterleaved) {
878         const int width = 1;
879         const int height = 5;
880
881         // Pure-color inputs, calculated using formulas 3.2, 3.3 and 3.4 from
882         // Rec. 709. (Except the first two, which are obvious given the 64–940
883         // range of luminance.)
884         unsigned expanded_data[width * height * 3] = {
885                  64, 512, 512,
886                 940, 512, 512,
887                 250, 409, 960,
888                 691, 167, 105,
889                 127, 960, 471,
890         };
891         float expected_data[4 * width * height] = {
892                 0.0, 0.0, 0.0, 1.0,
893                 1.0, 1.0, 1.0, 1.0,
894                 1.0, 0.0, 0.0, 1.0,
895                 0.0, 1.0, 0.0, 1.0,
896                 0.0, 0.0, 1.0, 1.0,
897         };
898         float out_data[4 * width * height];
899
900         // Pack 32:32:32 to 10:10:10:2.
901         uint32_t data[width * height];
902         for (unsigned i = 0; i < width * height; ++i) {
903                 data[i] =
904                          expanded_data[i * 3 + 0]        |
905                         (expanded_data[i * 3 + 1] << 10) |
906                         (expanded_data[i * 3 + 2] << 20);
907         }
908
909         EffectChainTester tester(nullptr, width, height);
910
911         ImageFormat format;
912         format.color_space = COLORSPACE_sRGB;
913         format.gamma_curve = GAMMA_sRGB;
914
915         YCbCrFormat ycbcr_format;
916         ycbcr_format.luma_coefficients = YCBCR_REC_709;
917         ycbcr_format.full_range = false;
918         ycbcr_format.num_levels = 1024;  // 10-bit.
919         ycbcr_format.chroma_subsampling_x = 1;
920         ycbcr_format.chroma_subsampling_y = 1;
921         ycbcr_format.cb_x_position = 0.5f;
922         ycbcr_format.cb_y_position = 0.5f;
923         ycbcr_format.cr_x_position = 0.5f;
924         ycbcr_format.cr_y_position = 0.5f;
925
926         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_INTERLEAVED, GL_UNSIGNED_INT_2_10_10_10_REV);
927         input->set_pixel_data(0, data);
928         tester.get_chain()->add_input(input);
929
930         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
931
932         // We can set much tighter limits on this than 8-bit Y'CbCr;
933         // even tighter than the default limits.
934         expect_equal(expected_data, out_data, 4 * width, height, 0.002, 0.0003);
935 }
936
937 TEST(YCbCrInputTest, TenBitPlanar) {
938         const int width = 1;
939         const int height = 5;
940
941         // The same data as TenBitInterleaved, but split.
942         uint16_t y[width * height] = {
943                  64,
944                 940,
945                 250,
946                 691,
947                 127,
948         };
949         uint16_t cb[width * height] = {
950                 512,
951                 512,
952                 409,
953                 167,
954                 960,
955         };
956         uint16_t cr[width * height] = {
957                 512,
958                 512,
959                 960,
960                 105,
961                 471,
962         };
963         float expected_data[4 * width * height] = {
964                 0.0, 0.0, 0.0, 1.0,
965                 1.0, 1.0, 1.0, 1.0,
966                 1.0, 0.0, 0.0, 1.0,
967                 0.0, 1.0, 0.0, 1.0,
968                 0.0, 0.0, 1.0, 1.0,
969         };
970         float out_data[4 * width * height];
971
972         EffectChainTester tester(nullptr, width, height);
973
974         ImageFormat format;
975         format.color_space = COLORSPACE_sRGB;
976         format.gamma_curve = GAMMA_sRGB;
977
978         YCbCrFormat ycbcr_format;
979         ycbcr_format.luma_coefficients = YCBCR_REC_709;
980         ycbcr_format.full_range = false;
981         ycbcr_format.num_levels = 1024;  // 10-bit.
982         ycbcr_format.chroma_subsampling_x = 1;
983         ycbcr_format.chroma_subsampling_y = 1;
984         ycbcr_format.cb_x_position = 0.5f;
985         ycbcr_format.cb_y_position = 0.5f;
986         ycbcr_format.cr_x_position = 0.5f;
987         ycbcr_format.cr_y_position = 0.5f;
988
989         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_PLANAR, GL_UNSIGNED_SHORT);
990         input->set_pixel_data(0, y);
991         input->set_pixel_data(1, cb);
992         input->set_pixel_data(2, cr);
993         tester.get_chain()->add_input(input);
994
995         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
996
997         // We can set much tighter limits on this than 8-bit Y'CbCr;
998         // even tighter than the default limits.
999         expect_equal(expected_data, out_data, 4 * width, height, 0.002, 0.0003);
1000 }
1001
1002 // Effectively scales down its input linearly by 4x (and repeating it),
1003 // which is not attainable without mipmaps.
1004 class MipmapNeedingEffect : public Effect {
1005 public:
1006         MipmapNeedingEffect() {}
1007         MipmapRequirements needs_mipmaps() const override { return NEEDS_MIPMAPS; }
1008
1009         // To be allowed to mess with the sampler state.
1010         bool needs_texture_bounce() const override { return true; }
1011
1012         string effect_type_id() const override { return "MipmapNeedingEffect"; }
1013         string output_fragment_shader() override { return read_file("mipmap_needing_effect.frag"); }
1014         void inform_added(EffectChain *chain) override { this->chain = chain; }
1015
1016         void set_gl_state(GLuint glsl_program_num, const string& prefix, unsigned *sampler_num) override
1017         {
1018                 Node *self = chain->find_node_for_effect(this);
1019                 glActiveTexture(chain->get_input_sampler(self, 0));
1020                 check_error();
1021                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
1022                 check_error();
1023                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
1024                 check_error();
1025         }
1026
1027 private:
1028         EffectChain *chain;
1029 };
1030
1031 // Basically the same test as EffectChainTest_MipmapGenerationWorks,
1032 // just with the data converted to Y'CbCr (as red only).
1033 TEST(EffectChainTest, MipmapGenerationWorks) {
1034         const unsigned width = 4;
1035         const unsigned height = 16;
1036         float red_data[width * height] = {  // In 4x4 blocks.
1037                 1.0f, 0.0f, 0.0f, 0.0f,
1038                 0.0f, 0.0f, 0.0f, 0.0f,
1039                 0.0f, 0.0f, 0.0f, 0.0f,
1040                 0.0f, 0.0f, 0.0f, 1.0f,
1041
1042                 0.0f, 0.0f, 0.0f, 0.0f,
1043                 0.0f, 0.5f, 0.0f, 0.0f,
1044                 0.0f, 0.0f, 1.0f, 0.0f,
1045                 0.0f, 0.0f, 0.0f, 0.0f,
1046
1047                 1.0f, 1.0f, 1.0f, 1.0f,
1048                 1.0f, 1.0f, 1.0f, 1.0f,
1049                 1.0f, 1.0f, 1.0f, 1.0f,
1050                 1.0f, 1.0f, 1.0f, 1.0f,
1051
1052                 0.0f, 0.0f, 0.0f, 0.0f,
1053                 0.0f, 1.0f, 1.0f, 0.0f,
1054                 0.0f, 1.0f, 1.0f, 0.0f,
1055                 0.0f, 0.0f, 0.0f, 0.0f,
1056         };
1057         float expected_data[width * height] = {  // Repeated four times each way.
1058                 0.125f,   0.125f,   0.125f,   0.125f,
1059                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
1060                 1.0f,     1.0f,     1.0f,     1.0f,
1061                 0.25f,    0.25f,    0.25f,    0.25f,
1062
1063                 0.125f,   0.125f,   0.125f,   0.125f,
1064                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
1065                 1.0f,     1.0f,     1.0f,     1.0f,
1066                 0.25f,    0.25f,    0.25f,    0.25f,
1067
1068                 0.125f,   0.125f,   0.125f,   0.125f,
1069                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
1070                 1.0f,     1.0f,     1.0f,     1.0f,
1071                 0.25f,    0.25f,    0.25f,    0.25f,
1072
1073                 0.125f,   0.125f,   0.125f,   0.125f,
1074                 0.09375f, 0.09375f, 0.09375f, 0.09375f,
1075                 1.0f,     1.0f,     1.0f,     1.0f,
1076                 0.25f,    0.25f,    0.25f,    0.25f,
1077         };
1078         float expected_data_rgba[width * height * 4];
1079         unsigned char ycbcr_data[width * height * 3];
1080
1081         // Convert to Y'CbCr.
1082         YCbCrFormat ycbcr_format;
1083         ycbcr_format.luma_coefficients = YCBCR_REC_709;
1084         ycbcr_format.full_range = false;
1085         ycbcr_format.num_levels = 256;
1086         ycbcr_format.chroma_subsampling_x = 1;
1087         ycbcr_format.chroma_subsampling_y = 1;
1088         ycbcr_format.cb_x_position = 0.5f;
1089         ycbcr_format.cb_y_position = 0.5f;
1090         ycbcr_format.cr_x_position = 0.5f;
1091         ycbcr_format.cr_y_position = 0.5f;
1092
1093         float offset[3];
1094         Eigen::Matrix3d ycbcr_to_rgb;
1095         compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
1096
1097         Eigen::Matrix3d rgb_to_ycbcr = ycbcr_to_rgb.inverse();
1098         for (unsigned i = 0; i < 64; ++i) {
1099                 Eigen::Vector3d rgb(red_data[i], 0.0, 0.0);
1100                 Eigen::Vector3d ycbcr = rgb_to_ycbcr * rgb;
1101                 ycbcr(0) += offset[0];
1102                 ycbcr(1) += offset[1];
1103                 ycbcr(2) += offset[2];
1104                 ycbcr_data[i * 3 + 0] = lrintf(ycbcr(0) * 255.0);
1105                 ycbcr_data[i * 3 + 1] = lrintf(ycbcr(1) * 255.0);
1106                 ycbcr_data[i * 3 + 2] = lrintf(ycbcr(2) * 255.0);
1107         }
1108
1109         // Expand expected_data to RGBA.
1110         for (unsigned i = 0; i < 64; ++i) {
1111                 expected_data_rgba[i * 4 + 0] = expected_data[i];
1112                 expected_data_rgba[i * 4 + 1] = 0.0f;
1113                 expected_data_rgba[i * 4 + 2] = 0.0f;
1114                 expected_data_rgba[i * 4 + 3] = 1.0f;
1115         }
1116
1117         ImageFormat format;
1118         format.color_space = COLORSPACE_sRGB;
1119         format.gamma_curve = GAMMA_sRGB;
1120
1121         float out_data[width * height * 4];
1122         EffectChainTester tester(nullptr, width, height);
1123         YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_INTERLEAVED);
1124         input->set_pixel_data(0, ycbcr_data);
1125         tester.get_chain()->add_input(input);
1126         tester.get_chain()->add_effect(new MipmapNeedingEffect());
1127         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
1128
1129         // The usual pretty loose limits.
1130         expect_equal(expected_data_rgba, out_data, width * 4, height, 0.025, 0.002);
1131 }
1132
1133 }  // namespace movit