Support conversion to and from the XYZ color space.
[movit] / deconvolution_sharpen_effect_test.cpp
1 // Unit tests for DeconvolutionSharpenEffect.
2
3 #include "test_util.h"
4 #include "gtest/gtest.h"
5 #include "deconvolution_sharpen_effect.h"
6
7 TEST(DeconvolutionSharpenEffectTest, IdentityTransformDoesNothing) {
8         const int size = 4;
9
10         float data[size * size] = {
11                 0.0, 1.0, 0.0, 1.0,
12                 0.0, 1.0, 1.0, 0.0,
13                 0.0, 0.5, 1.0, 0.5,
14                 0.0, 0.0, 0.0, 0.0,
15         };
16         float out_data[size * size];
17
18         EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
19         Effect *deconvolution_effect = tester.get_chain()->add_effect(new DeconvolutionSharpenEffect());
20         ASSERT_TRUE(deconvolution_effect->set_int("matrix_size", 5));
21         ASSERT_TRUE(deconvolution_effect->set_float("circle_radius", 0.0f));
22         ASSERT_TRUE(deconvolution_effect->set_float("gaussian_radius", 0.0f));
23         ASSERT_TRUE(deconvolution_effect->set_float("correlation", 0.0001f));
24         ASSERT_TRUE(deconvolution_effect->set_float("noise", 0.0f));
25         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
26
27         expect_equal(data, out_data, size, size);
28 }
29
30 TEST(DeconvolutionSharpenEffectTest, DeconvolvesCircularBlur) {
31         const int size = 13;
32
33         // Matches exactly a circular blur kernel with radius 2.0.
34         float data[size * size] = {
35                 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, 
36                 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, 
37                 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, 
38                 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, 
39                 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, 
40                 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, 
41                 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, 
42                 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, 
43                 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, 
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.000000, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
48         };
49         float expected_data[size * size] = {
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                 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                 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, 
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, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
63         };
64         float out_data[size * size];
65
66         EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
67         Effect *deconvolution_effect = tester.get_chain()->add_effect(new DeconvolutionSharpenEffect());
68         ASSERT_TRUE(deconvolution_effect->set_int("matrix_size", 5));
69         ASSERT_TRUE(deconvolution_effect->set_float("circle_radius", 2.0f));
70         ASSERT_TRUE(deconvolution_effect->set_float("gaussian_radius", 0.0f));
71         ASSERT_TRUE(deconvolution_effect->set_float("correlation", 0.0001f));
72         ASSERT_TRUE(deconvolution_effect->set_float("noise", 0.0f));
73         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
74
75         // The limits have to be quite lax; deconvolution is not an exact operation.
76         expect_equal(expected_data, out_data, size, size, 0.15f, 0.005f);
77 }
78
79 TEST(DeconvolutionSharpenEffectTest, DeconvolvesGaussianBlur) {
80         const int size = 13;
81         const float sigma = 0.5f;
82
83         float data[size * size], out_data[size * size];
84         float expected_data[] = {
85                 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, 
86                 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, 
87                 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, 
88                 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, 
89                 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, 
90                 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, 
91                 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, 
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, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
98         };
99
100         float sum = 0.0f;       
101         for (int y = 0; y < size; ++y) {
102                 for (int x = 0; x < size; ++x) {
103                         float z = hypot(x - 6, y - 6);
104                         data[y * size + x] = exp(-z*z / (2.0 * sigma * sigma)) / (2.0 * M_PI * sigma * sigma);
105                         sum += data[y * size + x];
106                 }
107         }
108         for (int y = 0; y < size; ++y) {
109                 for (int x = 0; x < size; ++x) {
110                         data[y * size + x] /= sum;
111                 }
112         }
113
114         EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
115         Effect *deconvolution_effect = tester.get_chain()->add_effect(new DeconvolutionSharpenEffect());
116         ASSERT_TRUE(deconvolution_effect->set_int("matrix_size", 5));
117         ASSERT_TRUE(deconvolution_effect->set_float("circle_radius", 0.0f));
118         ASSERT_TRUE(deconvolution_effect->set_float("gaussian_radius", sigma));
119         ASSERT_TRUE(deconvolution_effect->set_float("correlation", 0.0001f));
120         ASSERT_TRUE(deconvolution_effect->set_float("noise", 0.0f));
121         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
122
123         // We don't actually need to adjust the limits here; deconvolution of
124         // this kernel is pretty much exact.
125         expect_equal(expected_data, out_data, size, size);
126 }
127
128 TEST(DeconvolutionSharpenEffectTest, NoiseAndCorrelationControlsReduceNoiseBoosting) {
129         const int size = 13;
130         const float sigma = 0.5f;
131
132         float data[size * size], out_data[size * size];
133         float expected_data[size * size] = {
134                 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, 
135                 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, 
136                 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, 
137                 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, 
138                 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, 
139                 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, 
140                 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, 
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, 0.000000, 0.000000, 0.000000, 0.0, 0.0, 0.0, 0.0, 
147         };
148
149         // Gaussian kernel.
150         float sum = 0.0f;       
151         for (int y = 0; y < size; ++y) {
152                 for (int x = 0; x < size; ++x) {
153                         float z = hypot(x - 6, y - 6);
154                         data[y * size + x] = exp(-z*z / (2.0 * sigma * sigma)) / (2.0 * M_PI * sigma * sigma);
155                         sum += data[y * size + x];
156                 }
157         }
158         for (int y = 0; y < size; ++y) {
159                 for (int x = 0; x < size; ++x) {
160                         data[y * size + x] /= sum;
161                 }
162         }
163
164         // Corrupt with some uniform noise.
165         srand(1234);
166         for (int i = 0; i < size * size; ++i) {
167                 data[i] += 0.1 * ((float)rand() / RAND_MAX - 0.5);
168         }
169
170         EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
171         Effect *deconvolution_effect = tester.get_chain()->add_effect(new DeconvolutionSharpenEffect());
172         ASSERT_TRUE(deconvolution_effect->set_int("matrix_size", 5));
173         ASSERT_TRUE(deconvolution_effect->set_float("circle_radius", 0.0f));
174         ASSERT_TRUE(deconvolution_effect->set_float("gaussian_radius", 0.5f));
175         ASSERT_TRUE(deconvolution_effect->set_float("correlation", 0.5f));
176         ASSERT_TRUE(deconvolution_effect->set_float("noise", 0.1f));
177         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
178
179         float sumsq_in = 0.0f, sumsq_out = 0.0f;
180         for (int i = 0; i < size * size; ++i) {
181                 sumsq_in += data[i] * data[i];
182                 sumsq_out += out_data[i] * out_data[i];
183         }
184
185         // The limits have to be quite lax; deconvolution is not an exact operation.
186         // We special-case the center sample since it's the one with the largest error
187         // almost no matter what we do, so we don't want that to be the dominating
188         // factor in the outlier tests.
189         int center = size / 2;
190         EXPECT_GT(out_data[center * size + center], 0.5f);
191         out_data[center * size + center] = 1.0f;
192         expect_equal(expected_data, out_data, size, size, 0.20f, 0.005f);
193
194         // Check that we didn't boost total energy (which in this case means the noise) more than 10%.
195         EXPECT_LT(sumsq_out, sumsq_in * 1.1f);
196 }