Add an effect for Lanczos resampling.
[movit] / resample_effect_test.cpp
1 // Unit tests for ResampleEffect.
2
3 #include "test_util.h"
4 #include "gtest/gtest.h"
5 #include "resample_effect.h"
6 #include "flat_input.h"
7
8 namespace {
9
10 float sinc(float x)
11 {
12         return sin(M_PI * x) / (M_PI * x);
13 }
14
15 float lanczos(float x, float a)
16 {
17         if (fabs(x) >= a) {
18                 return 0.0f;
19         } else {
20                 return sinc(x) * sinc(x / a);
21         }
22 }
23
24 }  // namespace
25
26 TEST(ResampleEffectTest, IdentityTransformDoesNothing) {
27         const int size = 4;
28
29         float data[size * size] = {
30                 0.0, 1.0, 0.0, 1.0,
31                 0.0, 1.0, 1.0, 0.0,
32                 0.0, 0.5, 1.0, 0.5,
33                 0.0, 0.0, 0.0, 0.0,
34         };
35         float out_data[size * size];
36
37         EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
38         Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
39         ASSERT_TRUE(resample_effect->set_int("width", 4));
40         ASSERT_TRUE(resample_effect->set_int("height", 4));
41         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
42
43         expect_equal(data, out_data, size, size);
44 }
45
46 TEST(ResampleEffectTest, UpscaleByTwoGetsCorrectPixelCenters) {
47         const int size = 5;
48
49         float data[size * size] = {
50                 0.0, 0.0, 0.0, 0.0, 0.0,
51                 0.0, 0.0, 0.0, 0.0, 0.0,
52                 0.0, 0.0, 1.0, 0.0, 0.0,
53                 0.0, 0.0, 0.0, 0.0, 0.0,
54                 0.0, 0.0, 0.0, 0.0, 0.0,
55         };
56         float expected_data[size * size * 4], out_data[size * size * 4];
57
58         for (int y = 0; y < size * 2; ++y) {
59                 for (int x = 0; x < size * 2; ++x) {
60                         float weight = lanczos((x - size + 0.5f) * 0.5f, 3.0f);
61                         weight *= lanczos((y - size + 0.5f) * 0.5f, 3.0f);
62                         expected_data[y * (size * 2) + x] = weight;
63                 }
64         }
65
66         EffectChainTester tester(NULL, size * 2, size * 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
67
68         ImageFormat format;
69         format.color_space = COLORSPACE_sRGB;
70         format.gamma_curve = GAMMA_LINEAR;
71
72         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size, size);
73         input->set_pixel_data(data);
74         tester.get_chain()->add_input(input);
75
76         Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
77         ASSERT_TRUE(resample_effect->set_int("width", size * 2));
78         ASSERT_TRUE(resample_effect->set_int("height", size * 2));
79         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
80
81         expect_equal(expected_data, out_data, size * 2, size * 2);
82 }
83
84 TEST(ResampleEffectTest, DownscaleByTwoGetsCorrectPixelCenters) {
85         const int size = 5;
86
87         // This isn't a perfect dot, since the Lanczos filter has a slight
88         // sharpening effect; the most important thing is that we have kept
89         // the texel center right (everything is nicely symmetric).
90         // The approximate magnitudes have been checked against ImageMagick.
91         float expected_data[size * size] = {
92                  0.0045, -0.0067, -0.0598, -0.0067,  0.0045, 
93                 -0.0067,  0.0099,  0.0886,  0.0099, -0.0067, 
94                 -0.0598,  0.0886,  0.7930,  0.0886, -0.0598, 
95                 -0.0067,  0.0099,  0.0886,  0.0099, -0.0067, 
96                  0.0045, -0.0067, -0.0598, -0.0067,  0.0045, 
97         };
98         float data[size * size * 4], out_data[size * size];
99
100         for (int y = 0; y < size * 2; ++y) {
101                 for (int x = 0; x < size * 2; ++x) {
102                         float weight = lanczos((x - size + 0.5f) * 0.5f, 3.0f);
103                         weight *= lanczos((y - size + 0.5f) * 0.5f, 3.0f);
104                         data[y * (size * 2) + x] = weight;
105                 }
106         }
107
108         EffectChainTester tester(NULL, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
109
110         ImageFormat format;
111         format.color_space = COLORSPACE_sRGB;
112         format.gamma_curve = GAMMA_LINEAR;
113
114         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size * 2, size * 2);
115         input->set_pixel_data(data);
116         tester.get_chain()->add_input(input);
117
118         Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
119         ASSERT_TRUE(resample_effect->set_int("width", size));
120         ASSERT_TRUE(resample_effect->set_int("height", size));
121         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
122
123         expect_equal(expected_data, out_data, size, size);
124 }
125
126 TEST(ResampleEffectTest, UpscaleByThreeGetsCorrectPixelCenters) {
127         const int size = 5;
128
129         float data[size * size] = {
130                 0.0, 0.0, 0.0, 0.0, 0.0,
131                 0.0, 0.0, 0.0, 0.0, 0.0,
132                 0.0, 0.0, 1.0, 0.0, 0.0,
133                 0.0, 0.0, 0.0, 0.0, 0.0,
134                 0.0, 0.0, 0.0, 0.0, 0.0,
135         };
136         float out_data[size * size * 9];
137
138         EffectChainTester tester(NULL, size * 3, size * 3, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
139
140         ImageFormat format;
141         format.color_space = COLORSPACE_sRGB;
142         format.gamma_curve = GAMMA_LINEAR;
143
144         FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size, size);
145         input->set_pixel_data(data);
146         tester.get_chain()->add_input(input);
147
148         Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
149         ASSERT_TRUE(resample_effect->set_int("width", size * 3));
150         ASSERT_TRUE(resample_effect->set_int("height", size * 3));
151         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
152
153         // We only bother checking that the middle pixel is still correct,
154         // and that symmetry holds.
155         EXPECT_FLOAT_EQ(1.0, out_data[7 * (size * 3) + 7]);
156         for (unsigned y = 0; y < size * 3; ++y) {
157                 for (unsigned x = 0; x < size * 3; ++x) {
158                         EXPECT_FLOAT_EQ(out_data[y * (size * 3) + x], out_data[(size * 3 - y - 1) * (size * 3) + x]);
159                         EXPECT_FLOAT_EQ(out_data[y * (size * 3) + x], out_data[y * (size * 3) + (size * 3 - x - 1)]);
160                 }
161         }
162 }