1 // Unit tests for ResampleEffect.
4 #include <gtest/gtest.h>
7 #include "effect_chain.h"
8 #include "flat_input.h"
9 #include "image_format.h"
10 #include "resample_effect.h"
11 #include "test_util.h"
19 return sin(M_PI * x) / (M_PI * x);
22 float lanczos(float x, float a)
27 return sinc(x) * sinc(x / a);
33 TEST(ResampleEffectTest, IdentityTransformDoesNothing) {
36 float data[size * size] = {
42 float out_data[size * size];
44 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
45 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
46 ASSERT_TRUE(resample_effect->set_int("width", 4));
47 ASSERT_TRUE(resample_effect->set_int("height", 4));
48 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
50 expect_equal(data, out_data, size, size);
53 TEST(ResampleEffectTest, UpscaleByTwoGetsCorrectPixelCenters) {
56 float data[size * size] = {
57 0.0, 0.0, 0.0, 0.0, 0.0,
58 0.0, 0.0, 0.0, 0.0, 0.0,
59 0.0, 0.0, 1.0, 0.0, 0.0,
60 0.0, 0.0, 0.0, 0.0, 0.0,
61 0.0, 0.0, 0.0, 0.0, 0.0,
63 float expected_data[size * size * 4], out_data[size * size * 4];
65 for (int y = 0; y < size * 2; ++y) {
66 for (int x = 0; x < size * 2; ++x) {
67 float weight = lanczos((x - size + 0.5f) * 0.5f, 3.0f);
68 weight *= lanczos((y - size + 0.5f) * 0.5f, 3.0f);
69 expected_data[y * (size * 2) + x] = weight;
73 EffectChainTester tester(NULL, size * 2, size * 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
76 format.color_space = COLORSPACE_sRGB;
77 format.gamma_curve = GAMMA_LINEAR;
79 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size, size);
80 input->set_pixel_data(data);
81 tester.get_chain()->add_input(input);
83 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
84 ASSERT_TRUE(resample_effect->set_int("width", size * 2));
85 ASSERT_TRUE(resample_effect->set_int("height", size * 2));
86 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
88 expect_equal(expected_data, out_data, size * 2, size * 2);
91 TEST(ResampleEffectTest, DownscaleByTwoGetsCorrectPixelCenters) {
94 // This isn't a perfect dot, since the Lanczos filter has a slight
95 // sharpening effect; the most important thing is that we have kept
96 // the texel center right (everything is nicely symmetric).
97 // The approximate magnitudes have been checked against ImageMagick.
98 float expected_data[size * size] = {
99 0.0045, -0.0067, -0.0599, -0.0067, 0.0045,
100 -0.0067, 0.0100, 0.0892, 0.0100, -0.0067,
101 -0.0599, 0.0890, 0.7925, 0.0892, -0.0599,
102 -0.0067, 0.0100, 0.0890, 0.0100, -0.0067,
103 0.0045, -0.0067, -0.0599, -0.0067, 0.0045,
105 float data[size * size * 4], out_data[size * size];
107 for (int y = 0; y < size * 2; ++y) {
108 for (int x = 0; x < size * 2; ++x) {
109 float weight = lanczos((x - size + 0.5f) * 0.5f, 3.0f);
110 weight *= lanczos((y - size + 0.5f) * 0.5f, 3.0f);
111 data[y * (size * 2) + x] = weight;
115 EffectChainTester tester(NULL, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
118 format.color_space = COLORSPACE_sRGB;
119 format.gamma_curve = GAMMA_LINEAR;
121 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size * 2, size * 2);
122 input->set_pixel_data(data);
123 tester.get_chain()->add_input(input);
125 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
126 ASSERT_TRUE(resample_effect->set_int("width", size));
127 ASSERT_TRUE(resample_effect->set_int("height", size));
128 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
130 expect_equal(expected_data, out_data, size, size);
133 TEST(ResampleEffectTest, UpscaleByThreeGetsCorrectPixelCenters) {
136 float data[size * size] = {
137 0.0, 0.0, 0.0, 0.0, 0.0,
138 0.0, 0.0, 0.0, 0.0, 0.0,
139 0.0, 0.0, 1.0, 0.0, 0.0,
140 0.0, 0.0, 0.0, 0.0, 0.0,
141 0.0, 0.0, 0.0, 0.0, 0.0,
143 float out_data[size * size * 9];
145 EffectChainTester tester(NULL, size * 3, size * 3, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
148 format.color_space = COLORSPACE_sRGB;
149 format.gamma_curve = GAMMA_LINEAR;
151 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, size, size);
152 input->set_pixel_data(data);
153 tester.get_chain()->add_input(input);
155 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
156 ASSERT_TRUE(resample_effect->set_int("width", size * 3));
157 ASSERT_TRUE(resample_effect->set_int("height", size * 3));
158 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
160 // We only bother checking that the middle pixel is still correct,
161 // and that symmetry holds.
162 EXPECT_FLOAT_EQ(1.0, out_data[7 * (size * 3) + 7]);
163 for (unsigned y = 0; y < size * 3; ++y) {
164 for (unsigned x = 0; x < size * 3; ++x) {
165 EXPECT_NEAR(out_data[y * (size * 3) + x], out_data[(size * 3 - y - 1) * (size * 3) + x], 1e-6);
166 EXPECT_NEAR(out_data[y * (size * 3) + x], out_data[y * (size * 3) + (size * 3 - x - 1)], 1e-6);
171 TEST(ResampleEffectTest, HeavyResampleGetsSumRight) {
172 // Do only one resample pass, more specifically the last one, which goes to
173 // our fp32 output. This allows us to analyze the precision without intermediate
175 const int swidth = 1, sheight = 1280;
176 const int dwidth = 1, dheight = 64;
178 float data[swidth * sheight], out_data[dwidth * dheight], expected_data[dwidth * dheight];
179 for (int y = 0; y < sheight; ++y) {
180 for (int x = 0; x < swidth; ++x) {
181 data[y * swidth + x] = 1.0f;
184 for (int y = 0; y < dheight; ++y) {
185 for (int x = 0; x < dwidth; ++x) {
186 expected_data[y * dwidth + x] = 1.0f;
190 EffectChainTester tester(NULL, dwidth, dheight, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA32F);
193 format.color_space = COLORSPACE_sRGB;
194 format.gamma_curve = GAMMA_LINEAR;
196 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, swidth, sheight);
197 input->set_pixel_data(data);
199 tester.get_chain()->add_input(input);
200 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
201 ASSERT_TRUE(resample_effect->set_int("width", dwidth));
202 ASSERT_TRUE(resample_effect->set_int("height", dheight));
203 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
205 // Require that we are within 10-bit accuracy. Note that this limit is for
206 // one pass only, but the limit is tight enough that it should be good enough
207 // for 10-bit accuracy even after two passes.
208 expect_equal(expected_data, out_data, dwidth, dheight, 0.12 / 1023.0);
211 TEST(ResampleEffectTest, ReadWholePixelFromLeft) {
214 float data[size * size] = {
215 0.0, 0.0, 0.0, 0.0, 0.0,
216 0.0, 0.0, 0.0, 0.0, 0.0,
217 0.0, 0.0, 1.0, 0.0, 0.0,
218 0.0, 0.0, 0.0, 0.0, 0.0,
219 0.0, 0.0, 0.0, 0.0, 0.0,
221 float expected_data[size * size] = {
222 0.0, 0.0, 0.0, 0.0, 0.0,
223 0.0, 0.0, 0.0, 0.0, 0.0,
224 0.0, 1.0, 0.0, 0.0, 0.0,
225 0.0, 0.0, 0.0, 0.0, 0.0,
226 0.0, 0.0, 0.0, 0.0, 0.0,
228 float out_data[size * size];
230 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
231 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
232 ASSERT_TRUE(resample_effect->set_int("width", size));
233 ASSERT_TRUE(resample_effect->set_int("height", size));
234 ASSERT_TRUE(resample_effect->set_float("left", 1.0f));
235 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
237 expect_equal(expected_data, out_data, size, size);
240 TEST(ResampleEffectTest, ReadQuarterPixelFromLeft) {
243 float data[size * size] = {
244 0.0, 0.0, 0.0, 0.0, 0.0,
245 0.0, 0.0, 0.0, 0.0, 0.0,
246 0.0, 0.0, 1.0, 0.0, 0.0,
247 0.0, 0.0, 0.0, 0.0, 0.0,
248 0.0, 0.0, 0.0, 0.0, 0.0,
251 float expected_data[size * size] = {
252 0.0, 0.0, 0.0, 0.0, 0.0,
253 0.0, 0.0, 0.0, 0.0, 0.0,
255 // sin(x*pi)/(x*pi) * sin(x*pi/3)/(x*pi/3) for
256 // x = -1.75, -0.75, 0.25, 1.25, 2.25.
257 // Note that the weight is mostly on the left side.
258 -0.06779, 0.27019, 0.89007, -0.13287, 0.03002,
260 0.0, 0.0, 0.0, 0.0, 0.0,
261 0.0, 0.0, 0.0, 0.0, 0.0,
263 float out_data[size * size];
265 EffectChainTester tester(data, size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
266 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
267 ASSERT_TRUE(resample_effect->set_int("width", size));
268 ASSERT_TRUE(resample_effect->set_int("height", size));
269 ASSERT_TRUE(resample_effect->set_float("left", 0.25f));
270 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
272 expect_equal(expected_data, out_data, size, size);
275 TEST(ResampleEffectTest, ReadQuarterPixelFromTop) {
277 const int height = 5;
279 float data[width * height] = {
287 // See ReadQuarterPixelFromLeft for explanation of the data.
288 float expected_data[width * height] = {
295 float out_data[width * height];
297 EffectChainTester tester(data, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
298 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
299 ASSERT_TRUE(resample_effect->set_int("width", width));
300 ASSERT_TRUE(resample_effect->set_int("height", height));
301 ASSERT_TRUE(resample_effect->set_float("top", 0.25f));
302 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
304 expect_equal(expected_data, out_data, width, height);
307 TEST(ResampleEffectTest, ReadHalfPixelFromLeftAndScale) {
308 const int src_width = 4;
309 const int dst_width = 8;
311 float data[src_width * 1] = {
314 float expected_data[dst_width * 1] = {
315 // Empirical; the real test is that we are the same for 0.499 and 0.501.
316 1.1553, 1.7158, 2.2500, 2.7461, 3.2812, 3.8418, 4.0703, 4.0508
318 float out_data[dst_width * 1];
320 EffectChainTester tester(NULL, dst_width, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
323 format.color_space = COLORSPACE_sRGB;
324 format.gamma_curve = GAMMA_LINEAR;
326 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, src_width, 1);
327 input->set_pixel_data(data);
328 tester.get_chain()->add_input(input);
330 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
331 ASSERT_TRUE(resample_effect->set_int("width", dst_width));
332 ASSERT_TRUE(resample_effect->set_int("height", 1));
334 // Check that we are (almost) the same no matter the rounding.
335 ASSERT_TRUE(resample_effect->set_float("left", 0.499f));
336 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
337 expect_equal(expected_data, out_data, dst_width, 1, 1.5f / 255.0f, 0.4f / 255.0f);
339 ASSERT_TRUE(resample_effect->set_float("left", 0.501f));
340 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
341 expect_equal(expected_data, out_data, dst_width, 1, 1.5f / 255.0f, 0.4f / 255.0f);
344 TEST(ResampleEffectTest, Zoom) {
346 const int height = 3;
348 float data[width * height] = {
349 0.0, 0.0, 0.0, 0.0, 0.0,
350 0.2, 0.4, 0.6, 0.4, 0.2,
351 0.0, 0.0, 0.0, 0.0, 0.0,
353 float expected_data[width * height] = {
354 0.0, 0.0, 0.0, 0.0, 0.0,
355 0.4, 0.5396, 0.6, 0.5396, 0.4,
356 0.0, 0.0, 0.0, 0.0, 0.0,
358 float out_data[width * height];
360 EffectChainTester tester(data, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
361 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
362 ASSERT_TRUE(resample_effect->set_int("width", width));
363 ASSERT_TRUE(resample_effect->set_int("height", height));
364 ASSERT_TRUE(resample_effect->set_float("zoom_x", 2.0f));
365 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
367 expect_equal(expected_data, out_data, width, height);
370 TEST(ResampleEffectTest, VerticalZoomFromTop) {
372 const int height = 5;
374 float data[width * height] = {
375 0.2, 0.4, 0.6, 0.4, 0.2,
376 0.0, 0.0, 0.0, 0.0, 0.0,
377 0.0, 0.0, 0.0, 0.0, 0.0,
378 0.0, 0.0, 0.0, 0.0, 0.0,
379 0.0, 0.0, 0.0, 0.0, 0.0,
382 // Largely empirical data; the main point is that the top line
383 // is unchanged, since that's our zooming point.
384 float expected_data[width * height] = {
385 0.2000, 0.4000, 0.6000, 0.4000, 0.2000,
386 0.1389, 0.2778, 0.4167, 0.2778, 0.1389,
387 0.0600, 0.1199, 0.1798, 0.1199, 0.0600,
388 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
389 -0.0229, -0.0459, -0.0688, -0.0459, -0.0229,
391 float out_data[width * height];
393 EffectChainTester tester(data, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
394 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
395 ASSERT_TRUE(resample_effect->set_int("width", width));
396 ASSERT_TRUE(resample_effect->set_int("height", height));
397 ASSERT_TRUE(resample_effect->set_float("zoom_y", 3.0f));
398 ASSERT_TRUE(resample_effect->set_float("zoom_center_y", 0.5f / height));
399 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
401 expect_equal(expected_data, out_data, width, height);
404 TEST(ResampleEffectTest, Precision) {
405 const int size = 1920; // Difficult non-power-of-two size.
406 const int offset = 5;
408 // Deliberately put the data of interest very close to the right,
409 // where texture coordinates are farther from 0 and thus less precise.
410 float data[size * 2] = {0};
411 data[size - offset] = 1.0f;
412 float expected_data[size * 2] = {0};
413 for (int x = 0; x < size * 2; ++x) {
414 expected_data[x] = lanczos((x - (size - 2 * offset + 1) + 0.5f) * 0.5f, 3.0f);
416 float out_data[size * 2];
418 EffectChainTester tester(data, size * 2, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
419 Effect *resample_effect = tester.get_chain()->add_effect(new ResampleEffect());
420 ASSERT_TRUE(resample_effect->set_int("width", size * 2));
421 ASSERT_TRUE(resample_effect->set_int("height", 1));
422 ASSERT_TRUE(resample_effect->set_float("zoom_x", 2.0f));
423 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
425 expect_equal(expected_data, out_data, size, 1);