Merge branch 'master' into epoxy
[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 }  // namespace movit