Do our own fp16 conversion in ResampleEffect.
[movit] / resample_effect_test.cpp
1 // Unit tests for ResampleEffect.
2
3 #include <GL/glew.h>
4 #include <math.h>
5
6 #include "effect_chain.h"
7 #include "flat_input.h"
8 #include "glew.h"
9 #include "gtest/gtest.h"
10 #include "image_format.h"
11 #include "resample_effect.h"
12 #include "test_util.h"
13
14 namespace movit {
15
16 namespace {
17
18 float sinc(float x)
19 {
20         return sin(M_PI * x) / (M_PI * x);
21 }
22
23 float lanczos(float x, float a)
24 {
25         if (fabs(x) >= a) {
26                 return 0.0f;
27         } else {
28                 return sinc(x) * sinc(x / a);
29         }
30 }
31
32 }  // namespace
33
34 TEST(ResampleEffectTest, IdentityTransformDoesNothing) {
35         const int size = 4;
36
37         float data[size * size] = {
38                 0.0, 1.0, 0.0, 1.0,
39                 0.0, 1.0, 1.0, 0.0,
40                 0.0, 0.5, 1.0, 0.5,
41                 0.0, 0.0, 0.0, 0.0,
42         };
43         float out_data[size * size];
44
45         EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
46         Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
47         ASSERT_TRUE(resample_effect->set_int("width", 4));
48         ASSERT_TRUE(resample_effect->set_int("height", 4));
49         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
50
51         expect_equal(data, out_data, size, size);
52 }
53
54 TEST(ResampleEffectTest, UpscaleByTwoGetsCorrectPixelCenters) {
55         const int size = 5;
56
57         float data[size * size] = {
58                 0.0, 0.0, 0.0, 0.0, 0.0,
59                 0.0, 0.0, 0.0, 0.0, 0.0,
60                 0.0, 0.0, 1.0, 0.0, 0.0,
61                 0.0, 0.0, 0.0, 0.0, 0.0,
62                 0.0, 0.0, 0.0, 0.0, 0.0,
63         };
64         float expected_data[size * size * 4], out_data[size * size * 4];
65
66         for (int y = 0; y < size * 2; ++y) {
67                 for (int x = 0; x < size * 2; ++x) {
68                         float weight = lanczos((x - size + 0.5f) * 0.5f, 3.0f);
69                         weight *= lanczos((y - size + 0.5f) * 0.5f, 3.0f);
70                         expected_data[y * (size * 2) + x] = weight;
71                 }
72         }
73
74         EffectChainTester tester(NULL, size * 2, size * 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
75
76         ImageFormat format;
77         format.color_space = COLORSPACE_sRGB;
78         format.gamma_curve = GAMMA_LINEAR;
79
80         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size, size);
81         input->set_pixel_data(data);
82         tester.get_chain()->add_input(input);
83
84         Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
85         ASSERT_TRUE(resample_effect->set_int("width", size * 2));
86         ASSERT_TRUE(resample_effect->set_int("height", size * 2));
87         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
88
89         expect_equal(expected_data, out_data, size * 2, size * 2);
90 }
91
92 TEST(ResampleEffectTest, DownscaleByTwoGetsCorrectPixelCenters) {
93         const int size = 5;
94
95         // This isn't a perfect dot, since the Lanczos filter has a slight
96         // sharpening effect; the most important thing is that we have kept
97         // the texel center right (everything is nicely symmetric).
98         // The approximate magnitudes have been checked against ImageMagick.
99         float expected_data[size * size] = {
100                  0.0045, -0.0067, -0.0598, -0.0067,  0.0045, 
101                 -0.0067,  0.0099,  0.0886,  0.0099, -0.0067, 
102                 -0.0598,  0.0886,  0.7930,  0.0886, -0.0598, 
103                 -0.0067,  0.0099,  0.0886,  0.0099, -0.0067, 
104                  0.0045, -0.0067, -0.0598, -0.0067,  0.0045, 
105         };
106         float data[size * size * 4], out_data[size * size];
107
108         for (int y = 0; y < size * 2; ++y) {
109                 for (int x = 0; x < size * 2; ++x) {
110                         float weight = lanczos((x - size + 0.5f) * 0.5f, 3.0f);
111                         weight *= lanczos((y - size + 0.5f) * 0.5f, 3.0f);
112                         data[y * (size * 2) + x] = weight;
113                 }
114         }
115
116         EffectChainTester tester(NULL, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
117
118         ImageFormat format;
119         format.color_space = COLORSPACE_sRGB;
120         format.gamma_curve = GAMMA_LINEAR;
121
122         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size * 2, size * 2);
123         input->set_pixel_data(data);
124         tester.get_chain()->add_input(input);
125
126         Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
127         ASSERT_TRUE(resample_effect->set_int("width", size));
128         ASSERT_TRUE(resample_effect->set_int("height", size));
129         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
130
131         expect_equal(expected_data, out_data, size, size);
132 }
133
134 TEST(ResampleEffectTest, UpscaleByThreeGetsCorrectPixelCenters) {
135         const int size = 5;
136
137         float data[size * size] = {
138                 0.0, 0.0, 0.0, 0.0, 0.0,
139                 0.0, 0.0, 0.0, 0.0, 0.0,
140                 0.0, 0.0, 1.0, 0.0, 0.0,
141                 0.0, 0.0, 0.0, 0.0, 0.0,
142                 0.0, 0.0, 0.0, 0.0, 0.0,
143         };
144         float out_data[size * size * 9];
145
146         EffectChainTester tester(NULL, size * 3, size * 3, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
147
148         ImageFormat format;
149         format.color_space = COLORSPACE_sRGB;
150         format.gamma_curve = GAMMA_LINEAR;
151
152         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size, size);
153         input->set_pixel_data(data);
154         tester.get_chain()->add_input(input);
155
156         Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
157         ASSERT_TRUE(resample_effect->set_int("width", size * 3));
158         ASSERT_TRUE(resample_effect->set_int("height", size * 3));
159         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
160
161         // We only bother checking that the middle pixel is still correct,
162         // and that symmetry holds.
163         EXPECT_FLOAT_EQ(1.0, out_data[7 * (size * 3) + 7]);
164         for (unsigned y = 0; y < size * 3; ++y) {
165                 for (unsigned x = 0; x < size * 3; ++x) {
166                         EXPECT_FLOAT_EQ(out_data[y * (size * 3) + x], out_data[(size * 3 - y - 1) * (size * 3) + x]);
167                         EXPECT_FLOAT_EQ(out_data[y * (size * 3) + x], out_data[y * (size * 3) + (size * 3 - x - 1)]);
168                 }
169         }
170 }
171
172 TEST(ResampleEffectTest, HeavyResampleGetsSumRight) {
173         // Do only one resample pass, more specifically the last one, which goes to
174         // our fp32 output. This allows us to analyze the precision without intermediate
175         // fp16 rounding.
176         const int swidth = 1, sheight = 1280;
177         const int dwidth = 1, dheight = 64;
178
179         float data[swidth * sheight], out_data[dwidth * dheight], expected_data[dwidth * dheight];
180         for (int y = 0; y < sheight; ++y) {
181                 for (int x = 0; x < swidth; ++x) {
182                         data[y * swidth + x] = 1.0f;
183                 }
184         }
185         for (int y = 0; y < dheight; ++y) {
186                 for (int x = 0; x < dwidth; ++x) {
187                         expected_data[y * dwidth + x] = 1.0f;
188                 }
189         }
190
191         EffectChainTester tester(NULL, dwidth, dheight, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA32F);
192
193         ImageFormat format;
194         format.color_space = COLORSPACE_sRGB;
195         format.gamma_curve = GAMMA_LINEAR;
196
197         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, swidth, sheight);
198         input->set_pixel_data(data);
199
200         tester.get_chain()->add_input(input);
201         Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
202         ASSERT_TRUE(resample_effect->set_int("width", dwidth));
203         ASSERT_TRUE(resample_effect->set_int("height", dheight));
204         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
205
206         // Require that we are within 10-bit accuracy. Note that this limit is for
207         // one pass only, but the limit is tight enough that it should be good enough
208         // for 10-bit accuracy even after two passes.
209         expect_equal(expected_data, out_data, dwidth, dheight, 0.1 / 1023.0);
210 }
211
212 }  // namespace movit