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