Fix a small overallocation.
[movit] / deconvolution_sharpen_effect_test.cpp
1 // Unit tests for DeconvolutionSharpenEffect.
2
3 #include <GL/glew.h>
4 #include <math.h>
5 #include <stdlib.h>
6
7 #include "deconvolution_sharpen_effect.h"
8 #include "effect_chain.h"
9 #include "gtest/gtest.h"
10 #include "image_format.h"
11 #include "test_util.h"
12
13 namespace movit {
14
15 TEST(DeconvolutionSharpenEffectTest, IdentityTransformDoesNothing) {
16         const int size = 4;
17
18         float data[size * size] = {
19                 0.0, 1.0, 0.0, 1.0,
20                 0.0, 1.0, 1.0, 0.0,
21                 0.0, 0.5, 1.0, 0.5,
22                 0.0, 0.0, 0.0, 0.0,
23         };
24         float out_data[size * size];
25
26         EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
27         Effect *deconvolution_effect = tester.get_chain()->add_effect(new DeconvolutionSharpenEffect());
28         ASSERT_TRUE(deconvolution_effect->set_int("matrix_size", 5));
29         ASSERT_TRUE(deconvolution_effect->set_float("circle_radius", 0.0f));
30         ASSERT_TRUE(deconvolution_effect->set_float("gaussian_radius", 0.0f));
31         ASSERT_TRUE(deconvolution_effect->set_float("correlation", 0.0001f));
32         ASSERT_TRUE(deconvolution_effect->set_float("noise", 0.0f));
33         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
34
35         expect_equal(data, out_data, size, size);
36 }
37
38 TEST(DeconvolutionSharpenEffectTest, DeconvolvesCircularBlur) {
39         const int size = 13;
40
41         // Matches exactly a circular blur kernel with radius 2.0.
42         float data[size * size] = {
43                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
44                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
45                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
46                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
47                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.017016, 0.038115, 0.017016, 0.000000, 0.0, 0.0, 0.0, 0.0, 
48                 0.0, 0.0, 0.0, 0.0, 0.017016, 0.078381, 0.079577, 0.078381, 0.017016, 0.0, 0.0, 0.0, 0.0, 
49                 0.0, 0.0, 0.0, 0.0, 0.038115, 0.079577, 0.079577, 0.079577, 0.038115, 0.0, 0.0, 0.0, 0.0, 
50                 0.0, 0.0, 0.0, 0.0, 0.017016, 0.078381, 0.079577, 0.078381, 0.017016, 0.0, 0.0, 0.0, 0.0, 
51                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.017016, 0.038115, 0.017016, 0.000000, 0.0, 0.0, 0.0, 0.0, 
52                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
53                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
54                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
55                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
56         };
57         float expected_data[size * size] = {
58                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
59                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
60                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
61                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
62                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
63                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
64                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
65                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
66                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
67                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
68                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
69                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
70                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
71         };
72         float out_data[size * size];
73
74         EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
75         Effect *deconvolution_effect = tester.get_chain()->add_effect(new DeconvolutionSharpenEffect());
76         ASSERT_TRUE(deconvolution_effect->set_int("matrix_size", 5));
77         ASSERT_TRUE(deconvolution_effect->set_float("circle_radius", 2.0f));
78         ASSERT_TRUE(deconvolution_effect->set_float("gaussian_radius", 0.0f));
79         ASSERT_TRUE(deconvolution_effect->set_float("correlation", 0.0001f));
80         ASSERT_TRUE(deconvolution_effect->set_float("noise", 0.0f));
81         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
82
83         // The limits have to be quite lax; deconvolution is not an exact operation.
84         expect_equal(expected_data, out_data, size, size, 0.15f, 0.005f);
85 }
86
87 TEST(DeconvolutionSharpenEffectTest, DeconvolvesGaussianBlur) {
88         const int size = 13;
89         const float sigma = 0.5f;
90
91         float data[size * size], out_data[size * size];
92         float expected_data[] = {
93                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
94                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
95                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
96                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
97                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
98                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
99                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
100                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
101                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
102                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
103                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
104                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
105                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
106         };
107
108         float sum = 0.0f;       
109         for (int y = 0; y < size; ++y) {
110                 for (int x = 0; x < size; ++x) {
111                         float z = hypot(x - 6, y - 6);
112                         data[y * size + x] = exp(-z*z / (2.0 * sigma * sigma)) / (2.0 * M_PI * sigma * sigma);
113                         sum += data[y * size + x];
114                 }
115         }
116         for (int y = 0; y < size; ++y) {
117                 for (int x = 0; x < size; ++x) {
118                         data[y * size + x] /= sum;
119                 }
120         }
121
122         EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
123         Effect *deconvolution_effect = tester.get_chain()->add_effect(new DeconvolutionSharpenEffect());
124         ASSERT_TRUE(deconvolution_effect->set_int("matrix_size", 5));
125         ASSERT_TRUE(deconvolution_effect->set_float("circle_radius", 0.0f));
126         ASSERT_TRUE(deconvolution_effect->set_float("gaussian_radius", sigma));
127         ASSERT_TRUE(deconvolution_effect->set_float("correlation", 0.0001f));
128         ASSERT_TRUE(deconvolution_effect->set_float("noise", 0.0f));
129         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
130
131         // We don't actually need to adjust the limits here; deconvolution of
132         // this kernel is pretty much exact.
133         expect_equal(expected_data, out_data, size, size);
134 }
135
136 TEST(DeconvolutionSharpenEffectTest, NoiseAndCorrelationControlsReduceNoiseBoosting) {
137         const int size = 13;
138         const float sigma = 0.5f;
139
140         float data[size * size], out_data[size * size];
141         float expected_data[size * size] = {
142                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
143                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
144                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
145                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
146                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
147                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
148                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
149                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
150                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
151                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
152                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
153                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
154                 0.0, 0.0, 0.0, 0.0, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
155         };
156
157         // Gaussian kernel.
158         float sum = 0.0f;       
159         for (int y = 0; y < size; ++y) {
160                 for (int x = 0; x < size; ++x) {
161                         float z = hypot(x - 6, y - 6);
162                         data[y * size + x] = exp(-z*z / (2.0 * sigma * sigma)) / (2.0 * M_PI * sigma * sigma);
163                         sum += data[y * size + x];
164                 }
165         }
166         for (int y = 0; y < size; ++y) {
167                 for (int x = 0; x < size; ++x) {
168                         data[y * size + x] /= sum;
169                 }
170         }
171
172         // Corrupt with some uniform noise.
173         srand(1234);
174         for (int i = 0; i < size * size; ++i) {
175                 data[i] += 0.1 * ((float)rand() / RAND_MAX - 0.5);
176         }
177
178         EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
179         Effect *deconvolution_effect = tester.get_chain()->add_effect(new DeconvolutionSharpenEffect());
180         ASSERT_TRUE(deconvolution_effect->set_int("matrix_size", 5));
181         ASSERT_TRUE(deconvolution_effect->set_float("circle_radius", 0.0f));
182         ASSERT_TRUE(deconvolution_effect->set_float("gaussian_radius", 0.5f));
183         ASSERT_TRUE(deconvolution_effect->set_float("correlation", 0.5f));
184         ASSERT_TRUE(deconvolution_effect->set_float("noise", 0.1f));
185         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
186
187         float sumsq_in = 0.0f, sumsq_out = 0.0f;
188         for (int i = 0; i < size * size; ++i) {
189                 sumsq_in += data[i] * data[i];
190                 sumsq_out += out_data[i] * out_data[i];
191         }
192
193         // The limits have to be quite lax; deconvolution is not an exact operation.
194         // We special-case the center sample since it's the one with the largest error
195         // almost no matter what we do, so we don't want that to be the dominating
196         // factor in the outlier tests.
197         int center = size / 2;
198         EXPECT_GT(out_data[center * size + center], 0.5f);
199         out_data[center * size + center] = 1.0f;
200         expect_equal(expected_data, out_data, size, size, 0.20f, 0.005f);
201
202         // Check that we didn't boost total energy (which in this case means the noise) more than 10%.
203         EXPECT_LT(sumsq_out, sumsq_in * 1.1f);
204 }
205
206 TEST(DeconvolutionSharpenEffectTest, CircularDeconvolutionKeepsAlpha) {
207         // Somewhat bigger, to make sure we are much bigger than the matrix size.
208         const int size = 32;
209
210         float data[size * size * 4];
211         float out_data[size * size];
212         float expected_alpha[size * size];
213
214         // Checkerbox pattern.
215         for (int y = 0; y < size; ++y) {
216                 for (int x = 0; x < size; ++x) {
217                         int c = (y ^ x) & 1;
218                         data[(y * size + x) * 4 + 0] = c;
219                         data[(y * size + x) * 4 + 1] = c;
220                         data[(y * size + x) * 4 + 2] = c;
221                         data[(y * size + x) * 4 + 3] = 1.0;
222                         expected_alpha[y * size + x] = 1.0;
223                 }
224         }
225
226         EffectChainTester tester(data, size, size, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
227         Effect *deconvolution_effect = tester.get_chain()->add_effect(new DeconvolutionSharpenEffect());
228         ASSERT_TRUE(deconvolution_effect->set_int("matrix_size", 5));
229         ASSERT_TRUE(deconvolution_effect->set_float("circle_radius", 2.0f));
230         ASSERT_TRUE(deconvolution_effect->set_float("gaussian_radius", 0.0f));
231         ASSERT_TRUE(deconvolution_effect->set_float("correlation", 0.0001f));
232         ASSERT_TRUE(deconvolution_effect->set_float("noise", 0.0f));
233         tester.run(out_data, GL_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
234
235         expect_equal(expected_alpha, out_data, size, size);
236 }
237
238 }  // namespace movit