]> git.sesse.net Git - movit/blob - resample_effect_test.cpp
Release Movit 1.1.1.
[movit] / resample_effect_test.cpp
1 // Unit tests for ResampleEffect.
2
3 #include <epoxy/gl.h>
4 #include <gtest/gtest.h>
5 #include <math.h>
6
7 #include "effect_chain.h"
8 #include "flat_input.h"
9 #include "image_format.h"
10 #include "resample_effect.h"
11 #include "test_util.h"
12
13 namespace movit {
14
15 namespace {
16
17 float sinc(float x)
18 {
19         return sin(M_PI * x) / (M_PI * x);
20 }
21
22 float lanczos(float x, float a)
23 {
24         if (fabs(x) >= a) {
25                 return 0.0f;
26         } else {
27                 return sinc(x) * sinc(x / a);
28         }
29 }
30
31 }  // namespace
32
33 TEST(ResampleEffectTest, IdentityTransformDoesNothing) {
34         const int size = 4;
35
36         float data[size * size] = {
37                 0.0, 1.0, 0.0, 1.0,
38                 0.0, 1.0, 1.0, 0.0,
39                 0.0, 0.5, 1.0, 0.5,
40                 0.0, 0.0, 0.0, 0.0,
41         };
42         float out_data[size * size];
43
44         EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
45         Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
46         ASSERT_TRUE(resample_effect->set_int("width", 4));
47         ASSERT_TRUE(resample_effect->set_int("height", 4));
48         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
49
50         expect_equal(data, out_data, size, size);
51 }
52
53 TEST(ResampleEffectTest, UpscaleByTwoGetsCorrectPixelCenters) {
54         const int size = 5;
55
56         float data[size * size] = {
57                 0.0, 0.0, 0.0, 0.0, 0.0,
58                 0.0, 0.0, 0.0, 0.0, 0.0,
59                 0.0, 0.0, 1.0, 0.0, 0.0,
60                 0.0, 0.0, 0.0, 0.0, 0.0,
61                 0.0, 0.0, 0.0, 0.0, 0.0,
62         };
63         float expected_data[size * size * 4], out_data[size * size * 4];
64
65         for (int y = 0; y < size * 2; ++y) {
66                 for (int x = 0; x < size * 2; ++x) {
67                         float weight = lanczos((x - size + 0.5f) * 0.5f, 3.0f);
68                         weight *= lanczos((y - size + 0.5f) * 0.5f, 3.0f);
69                         expected_data[y * (size * 2) + x] = weight;
70                 }
71         }
72
73         EffectChainTester tester(NULL, size * 2, size * 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
74
75         ImageFormat format;
76         format.color_space = COLORSPACE_sRGB;
77         format.gamma_curve = GAMMA_LINEAR;
78
79         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size, size);
80         input->set_pixel_data(data);
81         tester.get_chain()->add_input(input);
82
83         Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
84         ASSERT_TRUE(resample_effect->set_int("width", size * 2));
85         ASSERT_TRUE(resample_effect->set_int("height", size * 2));
86         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
87
88         expect_equal(expected_data, out_data, size * 2, size * 2);
89 }
90
91 TEST(ResampleEffectTest, DownscaleByTwoGetsCorrectPixelCenters) {
92         const int size = 5;
93
94         // This isn't a perfect dot, since the Lanczos filter has a slight
95         // sharpening effect; the most important thing is that we have kept
96         // the texel center right (everything is nicely symmetric).
97         // The approximate magnitudes have been checked against ImageMagick.
98         float expected_data[size * size] = {
99                  0.0045, -0.0067, -0.0598, -0.0067,  0.0045, 
100                 -0.0067,  0.0099,  0.0886,  0.0099, -0.0067, 
101                 -0.0598,  0.0886,  0.7930,  0.0886, -0.0598, 
102                 -0.0067,  0.0099,  0.0886,  0.0099, -0.0067, 
103                  0.0045, -0.0067, -0.0598, -0.0067,  0.0045, 
104         };
105         float data[size * size * 4], out_data[size * size];
106
107         for (int y = 0; y < size * 2; ++y) {
108                 for (int x = 0; x < size * 2; ++x) {
109                         float weight = lanczos((x - size + 0.5f) * 0.5f, 3.0f);
110                         weight *= lanczos((y - size + 0.5f) * 0.5f, 3.0f);
111                         data[y * (size * 2) + x] = weight;
112                 }
113         }
114
115         EffectChainTester tester(NULL, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
116
117         ImageFormat format;
118         format.color_space = COLORSPACE_sRGB;
119         format.gamma_curve = GAMMA_LINEAR;
120
121         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size * 2, size * 2);
122         input->set_pixel_data(data);
123         tester.get_chain()->add_input(input);
124
125         Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
126         ASSERT_TRUE(resample_effect->set_int("width", size));
127         ASSERT_TRUE(resample_effect->set_int("height", size));
128         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
129
130         expect_equal(expected_data, out_data, size, size);
131 }
132
133 TEST(ResampleEffectTest, UpscaleByThreeGetsCorrectPixelCenters) {
134         const int size = 5;
135
136         float data[size * size] = {
137                 0.0, 0.0, 0.0, 0.0, 0.0,
138                 0.0, 0.0, 0.0, 0.0, 0.0,
139                 0.0, 0.0, 1.0, 0.0, 0.0,
140                 0.0, 0.0, 0.0, 0.0, 0.0,
141                 0.0, 0.0, 0.0, 0.0, 0.0,
142         };
143         float out_data[size * size * 9];
144
145         EffectChainTester tester(NULL, size * 3, size * 3, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
146
147         ImageFormat format;
148         format.color_space = COLORSPACE_sRGB;
149         format.gamma_curve = GAMMA_LINEAR;
150
151         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size, size);
152         input->set_pixel_data(data);
153         tester.get_chain()->add_input(input);
154
155         Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
156         ASSERT_TRUE(resample_effect->set_int("width", size * 3));
157         ASSERT_TRUE(resample_effect->set_int("height", size * 3));
158         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
159
160         // We only bother checking that the middle pixel is still correct,
161         // and that symmetry holds.
162         EXPECT_FLOAT_EQ(1.0, out_data[7 * (size * 3) + 7]);
163         for (unsigned y = 0; y < size * 3; ++y) {
164                 for (unsigned x = 0; x < size * 3; ++x) {
165                         EXPECT_FLOAT_EQ(out_data[y * (size * 3) + x], out_data[(size * 3 - y - 1) * (size * 3) + x]);
166                         EXPECT_FLOAT_EQ(out_data[y * (size * 3) + x], out_data[y * (size * 3) + (size * 3 - x - 1)]);
167                 }
168         }
169 }
170
171 TEST(ResampleEffectTest, HeavyResampleGetsSumRight) {
172         // Do only one resample pass, more specifically the last one, which goes to
173         // our fp32 output. This allows us to analyze the precision without intermediate
174         // fp16 rounding.
175         const int swidth = 1, sheight = 1280;
176         const int dwidth = 1, dheight = 64;
177
178         float data[swidth * sheight], out_data[dwidth * dheight], expected_data[dwidth * dheight];
179         for (int y = 0; y < sheight; ++y) {
180                 for (int x = 0; x < swidth; ++x) {
181                         data[y * swidth + x] = 1.0f;
182                 }
183         }
184         for (int y = 0; y < dheight; ++y) {
185                 for (int x = 0; x < dwidth; ++x) {
186                         expected_data[y * dwidth + x] = 1.0f;
187                 }
188         }
189
190         EffectChainTester tester(NULL, dwidth, dheight, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA32F);
191
192         ImageFormat format;
193         format.color_space = COLORSPACE_sRGB;
194         format.gamma_curve = GAMMA_LINEAR;
195
196         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, swidth, sheight);
197         input->set_pixel_data(data);
198
199         tester.get_chain()->add_input(input);
200         Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
201         ASSERT_TRUE(resample_effect->set_int("width", dwidth));
202         ASSERT_TRUE(resample_effect->set_int("height", dheight));
203         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
204
205         // Require that we are within 10-bit accuracy. Note that this limit is for
206         // one pass only, but the limit is tight enough that it should be good enough
207         // for 10-bit accuracy even after two passes.
208         expect_equal(expected_data, out_data, dwidth, dheight, 0.1 / 1023.0);
209 }
210
211 TEST(ResampleEffectTest, ReadWholePixelFromLeft) {
212         const int size = 5;
213
214         float data[size * size] = {
215                 0.0, 0.0, 0.0, 0.0, 0.0,
216                 0.0, 0.0, 0.0, 0.0, 0.0,
217                 0.0, 0.0, 1.0, 0.0, 0.0,
218                 0.0, 0.0, 0.0, 0.0, 0.0,
219                 0.0, 0.0, 0.0, 0.0, 0.0,
220         };
221         float expected_data[size * size] = {
222                 0.0, 0.0, 0.0, 0.0, 0.0,
223                 0.0, 0.0, 0.0, 0.0, 0.0,
224                 0.0, 1.0, 0.0, 0.0, 0.0,
225                 0.0, 0.0, 0.0, 0.0, 0.0,
226                 0.0, 0.0, 0.0, 0.0, 0.0,
227         };
228         float out_data[size * size];
229
230         EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
231         Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
232         ASSERT_TRUE(resample_effect->set_int("width", size));
233         ASSERT_TRUE(resample_effect->set_int("height", size));
234         ASSERT_TRUE(resample_effect->set_float("left", 1.0f));
235         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
236
237         expect_equal(expected_data, out_data, size, size);
238 }
239
240 TEST(ResampleEffectTest, ReadQuarterPixelFromLeft) {
241         const int size = 5;
242
243         float data[size * size] = {
244                 0.0, 0.0, 0.0, 0.0, 0.0,
245                 0.0, 0.0, 0.0, 0.0, 0.0,
246                 0.0, 0.0, 1.0, 0.0, 0.0,
247                 0.0, 0.0, 0.0, 0.0, 0.0,
248                 0.0, 0.0, 0.0, 0.0, 0.0,
249         };
250
251         float expected_data[size * size] = {
252                 0.0, 0.0, 0.0, 0.0, 0.0,
253                 0.0, 0.0, 0.0, 0.0, 0.0,
254
255                 // sin(x*pi)/(x*pi) * sin(x*pi/3)/(x*pi/3) for
256                 // x = -1.75, -0.75, 0.25, 1.25, 2.25.
257                 // Note that the weight is mostly on the left side.
258                 -0.06779, 0.27019, 0.89007, -0.13287, 0.03002,
259
260                 0.0, 0.0, 0.0, 0.0, 0.0,
261                 0.0, 0.0, 0.0, 0.0, 0.0,
262         };
263         float out_data[size * size];
264
265         EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
266         Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
267         ASSERT_TRUE(resample_effect->set_int("width", size));
268         ASSERT_TRUE(resample_effect->set_int("height", size));
269         ASSERT_TRUE(resample_effect->set_float("left", 0.25f));
270         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
271
272         expect_equal(expected_data, out_data, size, size);
273 }
274
275 TEST(ResampleEffectTest, ReadQuarterPixelFromTop) {
276         const int width = 3;
277         const int height = 5;
278
279         float data[width * height] = {
280                 0.0, 0.0, 0.0,
281                 0.0, 0.0, 0.0,
282                 1.0, 0.0, 0.0,
283                 0.0, 0.0, 0.0,
284                 0.0, 0.0, 0.0,
285         };
286
287         // See ReadQuarterPixelFromLeft for explanation of the data.
288         float expected_data[width * height] = {
289                 -0.06779, 0.0, 0.0,
290                  0.27019, 0.0, 0.0,
291                  0.89007, 0.0, 0.0,
292                 -0.13287, 0.0, 0.0,
293                  0.03002, 0.0, 0.0,
294         };
295         float out_data[width * height];
296
297         EffectChainTester tester(data, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
298         Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
299         ASSERT_TRUE(resample_effect->set_int("width", width));
300         ASSERT_TRUE(resample_effect->set_int("height", height));
301         ASSERT_TRUE(resample_effect->set_float("top", 0.25f));
302         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
303
304         expect_equal(expected_data, out_data, width, height);
305 }
306
307 TEST(ResampleEffectTest, ReadHalfPixelFromLeftAndScale) {
308         const int src_width = 4;
309         const int dst_width = 8;
310
311         float data[src_width * 1] = {
312                 1.0, 2.0, 3.0, 4.0,
313         };
314         float expected_data[dst_width * 1] = {
315                 // Empirical; the real test is that we are the same for 0.499 and 0.501.
316                 1.1553, 1.7158, 2.2500, 2.7461, 3.2812, 3.8418, 4.0703, 4.0508
317         };
318         float out_data[dst_width * 1];
319
320         EffectChainTester tester(NULL, dst_width, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
321
322         ImageFormat format;
323         format.color_space = COLORSPACE_sRGB;
324         format.gamma_curve = GAMMA_LINEAR;
325
326         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, src_width, 1);
327         input->set_pixel_data(data);
328         tester.get_chain()->add_input(input);
329
330         Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
331         ASSERT_TRUE(resample_effect->set_int("width", dst_width));
332         ASSERT_TRUE(resample_effect->set_int("height", 1));
333
334         // Check that we are (almost) the same no matter the rounding.
335         ASSERT_TRUE(resample_effect->set_float("left", 0.499f));
336         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
337         expect_equal(expected_data, out_data, dst_width, 1, 1.5f / 255.0f, 0.4f / 255.0f);
338
339         ASSERT_TRUE(resample_effect->set_float("left", 0.501f));
340         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
341         expect_equal(expected_data, out_data, dst_width, 1, 1.5f / 255.0f, 0.4f / 255.0f);
342 }
343
344 TEST(ResampleEffectTest, Zoom) {
345         const int width = 5;
346         const int height = 3;
347
348         float data[width * height] = {
349                 0.0, 0.0, 0.0, 0.0, 0.0,
350                 0.2, 0.4, 0.6, 0.4, 0.2,
351                 0.0, 0.0, 0.0, 0.0, 0.0,
352         };
353         float expected_data[width * height] = {
354                 0.0, 0.0,    0.0, 0.0,    0.0,
355                 0.4, 0.5396, 0.6, 0.5396, 0.4,
356                 0.0, 0.0,    0.0, 0.0,    0.0,
357         };
358         float out_data[width * height];
359
360         EffectChainTester tester(data, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
361         Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
362         ASSERT_TRUE(resample_effect->set_int("width", width));
363         ASSERT_TRUE(resample_effect->set_int("height", height));
364         ASSERT_TRUE(resample_effect->set_float("zoom_x", 2.0f));
365         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
366
367         expect_equal(expected_data, out_data, width, height);
368 }
369
370 TEST(ResampleEffectTest, VerticalZoomFromTop) {
371         const int width = 5;
372         const int height = 5;
373
374         float data[width * height] = {
375                 0.2, 0.4, 0.6, 0.4, 0.2,
376                 0.0, 0.0, 0.0, 0.0, 0.0,
377                 0.0, 0.0, 0.0, 0.0, 0.0,
378                 0.0, 0.0, 0.0, 0.0, 0.0,
379                 0.0, 0.0, 0.0, 0.0, 0.0,
380         };
381
382         // Largely empirical data; the main point is that the top line
383         // is unchanged, since that's our zooming point.
384         float expected_data[width * height] = {
385                  0.2000,  0.4000,  0.6000,  0.4000,  0.2000,
386                  0.1389,  0.2778,  0.4167,  0.2778,  0.1389,
387                  0.0600,  0.1199,  0.1798,  0.1199,  0.0600,
388                  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,
389                 -0.0229, -0.0459, -0.0688, -0.0459, -0.0229,
390         };
391         float out_data[width * height];
392
393         EffectChainTester tester(data, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
394         Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
395         ASSERT_TRUE(resample_effect->set_int("width", width));
396         ASSERT_TRUE(resample_effect->set_int("height", height));
397         ASSERT_TRUE(resample_effect->set_float("zoom_y", 3.0f));
398         ASSERT_TRUE(resample_effect->set_float("zoom_center_y", 0.5f / height));
399         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
400
401         expect_equal(expected_data, out_data, width, height);
402 }
403
404 }  // namespace movit