• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2018, Alliance for Open Media. All rights reserved
3  *
4  * This source code is subject to the terms of the BSD 2 Clause License and
5  * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
6  * was not distributed with this source code in the LICENSE file, you can
7  * obtain it at www.aomedia.org/license/software. If the Alliance for Open
8  * Media Patent License 1.0 was not distributed with this source code in the
9  * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
10  */
11 
12 #include <stdbool.h>
13 #include "aom_mem/aom_mem.h"
14 #include "av1/encoder/rdopt.h"
15 #include "test/util.h"
16 #include "third_party/googletest/src/googletest/include/gtest/gtest.h"
17 
18 namespace {
19 
20 using ::testing::get;
21 using ::testing::tuple;
22 
get_pix(uint8_t * buf,int i,bool high_bd)23 static int get_pix(uint8_t *buf, int i, bool high_bd) {
24   if (high_bd) {
25     return *CONVERT_TO_SHORTPTR(buf + i);
26   } else {
27     return buf[i];
28   }
29 }
30 
31 /** Get the (i, j) value from the input; if i or j is outside of the width
32  * or height, the nearest pixel value is returned.
33  */
get_nearest_pix(const int * buf,int w,int h,int i,int j)34 static int get_nearest_pix(const int *buf, int w, int h, int i, int j) {
35   int offset = AOMMAX(AOMMIN(i, w - 1), 0) + w * AOMMAX(AOMMIN(j, h - 1), 0);
36   return buf[offset];
37 }
38 
39 /** Given the image data, creates a new image with padded values, so an
40  * 8-tap filter can be convolved. The padded value is the same as the closest
41  * value in the image. Returns a pointer to the start of the image in the
42  * padded data. Must be freed with free_pad_8tap. The output will be either
43  * 8-bit or 16-bit, depending on the high bit-depth (high_bd) field.
44  */
pad_8tap_convolve(const int * data,int w,int h,bool high_bd)45 static uint8_t *pad_8tap_convolve(const int *data, int w, int h, bool high_bd) {
46   // SIMD optimizations require the width to be a multiple of 8 and the height
47   // to be multiples of 4.
48   assert(w % 8 == 0);
49   assert(h % 4 == 0);
50   // For an 8-tap filter, we need to pad with 3 lines on top and on the left,
51   // and 4 lines on the right and bottom, for 7 extra lines.
52   const int pad_w = w + 7;
53   const int pad_h = h + 7;
54 
55   uint8_t *dst;
56   if (high_bd) {
57     dst =
58         CONVERT_TO_BYTEPTR(aom_memalign(32, sizeof(uint16_t) * pad_w * pad_h));
59   } else {
60     dst = (uint8_t *)aom_memalign(32, sizeof(uint8_t) * pad_w * pad_h);
61   }
62   for (int j = 0; j < pad_h; ++j) {
63     for (int i = 0; i < pad_w; ++i) {
64       const int v = get_nearest_pix(data, w, h, i - 3, j - 3);
65       if (high_bd) {
66         *CONVERT_TO_SHORTPTR(dst + i + j * pad_w) = v;
67       } else {
68         dst[i + j * pad_w] = v;
69       }
70     }
71   }
72   return dst + (w + 7) * 3 + 3;
73 }
74 
stride_8tap(int width)75 static int stride_8tap(int width) { return width + 7; }
76 
free_pad_8tap(uint8_t * padded,int width,bool high_bd)77 static void free_pad_8tap(uint8_t *padded, int width, bool high_bd) {
78   if (high_bd) {
79     aom_free(CONVERT_TO_SHORTPTR(padded - (width + 7) * 3 - 3));
80   } else {
81     aom_free(padded - (width + 7) * 3 - 3);
82   }
83 }
84 
malloc_bd(int num_entries,bool high_bd)85 static uint8_t *malloc_bd(int num_entries, bool high_bd) {
86   const int bytes_per_entry = high_bd ? sizeof(uint16_t) : sizeof(uint8_t);
87 
88   uint8_t *buf = (uint8_t *)aom_memalign(32, bytes_per_entry * num_entries);
89   if (high_bd) {
90     return CONVERT_TO_BYTEPTR(buf);
91   } else {
92     return buf;
93   }
94 }
95 
free_bd(uint8_t * p,bool high_bd)96 static void free_bd(uint8_t *p, bool high_bd) {
97   if (high_bd) {
98     aom_free(CONVERT_TO_SHORTPTR(p));
99   } else {
100     aom_free(p);
101   }
102 }
103 
104 class EdgeDetectBrightnessTest :
105     // Parameters are (brightness, width, height, high bit depth representation,
106     // bit depth).
107     public ::testing::TestWithParam<tuple<int, int, int, bool, int> > {
108  protected:
SetUp()109   void SetUp() override {
110     // Allocate a (width by height) array of luma values in orig_.
111     // padded_ will be filled by the pad() call, which adds a border around
112     // the orig_. The output_ array has enough space for the computation.
113     const int brightness = GET_PARAM(0);
114     const int width = GET_PARAM(1);
115     const int height = GET_PARAM(2);
116     const bool high_bd = GET_PARAM(3);
117 
118     // Create the padded image of uniform brightness.
119     int *orig = (int *)malloc(width * height * sizeof(int));
120     for (int i = 0; i < width * height; ++i) {
121       orig[i] = brightness;
122     }
123     input_ = pad_8tap_convolve(orig, width, height, high_bd);
124     free(orig);
125     output_ = malloc_bd(width * height, high_bd);
126   }
127 
TearDown()128   void TearDown() override {
129     const int width = GET_PARAM(1);
130     const bool high_bd = GET_PARAM(3);
131     free_pad_8tap(input_, width, high_bd);
132     free_bd(output_, high_bd);
133   }
134 
135   // Skip the tests where brightness exceeds the bit-depth; we run into this
136   // issue because of gtest's limitation on valid combinations of test
137   // parameters. Also skip the tests where bit depth is greater than 8, but
138   // high bit depth representation is not set.
should_skip() const139   bool should_skip() const {
140     const int brightness = GET_PARAM(0);
141     const int bd = GET_PARAM(4);
142     if (brightness >= (1 << bd)) {
143       return true;
144     }
145     const bool high_bd = GET_PARAM(3);
146     if (bd > 8 && !high_bd) {
147       return true;
148     }
149     return false;
150   }
151 
152   uint8_t *input_;
153   uint8_t *output_;
154 };
155 
TEST_P(EdgeDetectBrightnessTest,BlurUniformBrightness)156 TEST_P(EdgeDetectBrightnessTest, BlurUniformBrightness) {
157   // Some combination of parameters are non-sensical, due to limitations
158   // of the testing framework. Ignore these.
159   if (should_skip()) {
160     return;
161   }
162 
163   // For varying levels of brightness, the algorithm should
164   // produce the same output.
165   const int brightness = GET_PARAM(0);
166   const int width = GET_PARAM(1);
167   const int height = GET_PARAM(2);
168   const bool high_bd = GET_PARAM(3);
169   const int bd = GET_PARAM(4);
170 
171   gaussian_blur(input_, stride_8tap(width), width, height, output_, high_bd,
172                 bd);
173   for (int i = 0; i < width * height; ++i) {
174     ASSERT_EQ(brightness, get_pix(output_, i, high_bd));
175   }
176 }
177 
178 // No edges on a uniformly bright image.
TEST_P(EdgeDetectBrightnessTest,DetectUniformBrightness)179 TEST_P(EdgeDetectBrightnessTest, DetectUniformBrightness) {
180   if (should_skip()) {
181     return;
182   }
183   const int width = GET_PARAM(1);
184   const int height = GET_PARAM(2);
185   const bool high_bd = GET_PARAM(3);
186   const int bd = GET_PARAM(4);
187 
188   ASSERT_EQ(
189       0, av1_edge_exists(input_, stride_8tap(width), width, height, high_bd, bd)
190              .magnitude);
191 }
192 
193 INSTANTIATE_TEST_CASE_P(ImageBrightnessTests, EdgeDetectBrightnessTest,
194                         ::testing::Combine(
195                             // Brightness
196                             ::testing::Values(0, 1, 2, 127, 128, 129, 254, 255,
197                                               256, 511, 512, 1023, 1024, 2048,
198                                               4095),
199                             // Width
200                             ::testing::Values(8, 16, 32),
201                             // Height
202                             ::testing::Values(4, 8, 12, 32),
203                             // High bit depth representation
204                             ::testing::Bool(),
205                             // Bit depth
206                             ::testing::Values(8, 10, 12)));
207 
208 class EdgeDetectImageTest :
209     // Parameters are (width, height, high bit depth representation, bit depth).
210     public ::testing::TestWithParam<tuple<int, int, bool, int> > {
211  protected:
212   // Skip the tests where bit depth is greater than 8, but high bit depth
213   // representation is not set (limitation of testing framework).
should_skip() const214   bool should_skip() const {
215     const bool high_bd = GET_PARAM(2);
216     const int bd = GET_PARAM(3);
217     return bd > 8 && !high_bd;
218   }
219 };
220 
221 // Generate images with black on one side and white on the other.
TEST_P(EdgeDetectImageTest,BlackWhite)222 TEST_P(EdgeDetectImageTest, BlackWhite) {
223   // Some combination of parameters are non-sensical, due to limitations
224   // of the testing framework. Ignore these.
225   if (should_skip()) {
226     return;
227   }
228 
229   const int width = GET_PARAM(0);
230   const int height = GET_PARAM(1);
231   const bool high_bd = GET_PARAM(2);
232   const int bd = GET_PARAM(3);
233 
234   const int white = (1 << bd) - 1;
235   int *orig = (int *)malloc(width * height * sizeof(int));
236   for (int j = 0; j < height; ++j) {
237     for (int i = 0; i < width; ++i) {
238       if (i < width / 2) {
239         orig[i + j * width] = 0;
240       } else {
241         orig[i + j * width] = white;
242       }
243     }
244   }
245   uint8_t *padded = pad_8tap_convolve(orig, width, height, high_bd);
246   free(orig);
247   // Value should be between 556 and 560.
248   ASSERT_LE(556, av1_edge_exists(padded, stride_8tap(width), width, height,
249                                  high_bd, bd)
250                      .magnitude);
251   ASSERT_GE(560, av1_edge_exists(padded, stride_8tap(width), width, height,
252                                  high_bd, bd)
253                      .magnitude);
254 
255   free_pad_8tap(padded, width, high_bd);
256 }
257 
258 // Hardcoded blur tests.
259 static const int luma[32] = { 241, 147, 7,   90,  184, 103, 28,  186,
260                               2,   248, 49,  242, 114, 146, 127, 22,
261                               121, 228, 167, 108, 158, 174, 41,  168,
262                               214, 99,  184, 109, 114, 247, 117, 119 };
263 static const uint8_t expected[] = { 161, 138, 119, 118, 123, 118, 113, 122,
264                                     143, 140, 134, 133, 134, 126, 116, 114,
265                                     147, 149, 145, 142, 143, 138, 126, 118,
266                                     164, 156, 148, 144, 148, 148, 138, 126 };
267 
hardcoded_blur_test_aux(const bool high_bd)268 static void hardcoded_blur_test_aux(const bool high_bd) {
269   const int w = 8;
270   const int h = 4;
271   for (int bd = 8; bd <= 12; bd += 2) {
272     // Skip the tests where bit depth is greater than 8, but high bit depth
273     // representation is not set.
274     if (bd > 8 && !high_bd) {
275       break;
276     }
277     uint8_t *output = malloc_bd(w * h, high_bd);
278     uint8_t *padded = pad_8tap_convolve(luma, w, h, high_bd);
279     gaussian_blur(padded, stride_8tap(w), w, h, output, high_bd, bd);
280     for (int i = 0; i < w * h; ++i) {
281       ASSERT_EQ(expected[i], get_pix(output, i, high_bd));
282     }
283     free_pad_8tap(padded, w, high_bd);
284     free_bd(output, high_bd);
285 
286     // If we multiply the inputs by a constant factor, the output should not
287     // vary more than 0.5 * factor.
288     for (int c = 2; c < (1 << (bd - 8)); ++c) {
289       int scaled_luma[32];
290       for (int i = 0; i < 32; ++i) {
291         scaled_luma[i] = luma[i] * c;
292       }
293       uint8_t *output = malloc_bd(w * h, high_bd);
294       uint8_t *padded = pad_8tap_convolve(scaled_luma, w, h, high_bd);
295       gaussian_blur(padded, stride_8tap(w), w, h, output, high_bd, bd);
296       for (int i = 0; i < w * h; ++i) {
297         ASSERT_GE(c / 2, abs(expected[i] * c - get_pix(output, i, high_bd)));
298       }
299       free_pad_8tap(padded, w, high_bd);
300       free_bd(output, high_bd);
301     }
302   }
303 }
304 
TEST(EdgeDetectImageTest,HardcodedBlurTest)305 TEST(EdgeDetectImageTest, HardcodedBlurTest) {
306   hardcoded_blur_test_aux(false);
307   hardcoded_blur_test_aux(true);
308 }
309 
TEST(EdgeDetectImageTest,SobelTest)310 TEST(EdgeDetectImageTest, SobelTest) {
311   // Randomly generated 3x3. Compute Sobel for middle value.
312   const uint8_t buf[9] = { 241, 147, 7, 90, 184, 103, 28, 186, 2 };
313   const int stride = 3;
314   bool high_bd = false;
315   sobel_xy result = sobel(buf, stride, 1, 1, high_bd);
316   ASSERT_EQ(234, result.x);
317   ASSERT_EQ(140, result.y);
318 
319   // Verify it works for 8-bit values in a high bit-depth buffer.
320   const uint16_t buf8_16[9] = { 241, 147, 7, 90, 184, 103, 28, 186, 2 };
321   high_bd = true;
322   result = sobel(CONVERT_TO_BYTEPTR(buf8_16), stride, 1, 1, high_bd);
323   ASSERT_EQ(234, result.x);
324   ASSERT_EQ(140, result.y);
325 
326   // Verify it works for high bit-depth values as well.
327   const uint16_t buf16[9] = { 241, 147, 7, 90, 184, 2003, 1028, 186, 2 };
328   result = sobel(CONVERT_TO_BYTEPTR(buf16), stride, 1, 1, high_bd);
329   ASSERT_EQ(-2566, result.x);
330   ASSERT_EQ(-860, result.y);
331 }
332 
333 INSTANTIATE_TEST_CASE_P(EdgeDetectImages, EdgeDetectImageTest,
334                         ::testing::Combine(
335                             // Width
336                             ::testing::Values(8, 16, 32),
337                             // Height
338                             ::testing::Values(4, 8, 12, 32),
339                             // High bit depth representation
340                             ::testing::Bool(),
341                             // Bit depth
342                             ::testing::Values(8, 10, 12)));
343 }  // namespace
344