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