add edge detector to rdopt

Change-Id: If40fb06008b0b960a6dd55b40be14ba76587d311
diff --git a/aom/aomcx.h b/aom/aomcx.h
index 9955ed8..9703cf8 100644
--- a/aom/aomcx.h
+++ b/aom/aomcx.h
@@ -1080,7 +1080,7 @@
 AOM_CTRL_USE_TYPE(AV1E_SET_MTU, unsigned int)
 #define AOM_CTRL_AV1E_SET_MTU
 
-AOM_CTRL_USE_TYPE(AV1E_SET_TIMING_INFO_TYPE, aom_timing_info_type_t)
+AOM_CTRL_USE_TYPE(AV1E_SET_TIMING_INFO_TYPE, int) /* aom_timing_info_type_t */
 #define AOM_CTRL_AV1E_SET_TIMING_INFO_TYPE
 
 AOM_CTRL_USE_TYPE(AV1E_SET_ENABLE_DF, unsigned int)
diff --git a/av1/common/txb_common.h b/av1/common/txb_common.h
index 1dda51f..698e95b 100644
--- a/av1/common/txb_common.h
+++ b/av1/common/txb_common.h
@@ -12,6 +12,8 @@
 #ifndef AOM_AV1_COMMON_TXB_COMMON_H_
 #define AOM_AV1_COMMON_TXB_COMMON_H_
 
+#include "av1/common/onyxc_int.h"
+
 extern const int16_t k_eob_group_start[12];
 extern const int16_t k_eob_offset_bits[12];
 
diff --git a/av1/encoder/rdopt.c b/av1/encoder/rdopt.c
index 8f7526f..c65d6d4 100644
--- a/av1/encoder/rdopt.c
+++ b/av1/encoder/rdopt.c
@@ -30,6 +30,7 @@
 #include "av1/common/idct.h"
 #include "av1/common/mvref_common.h"
 #include "av1/common/obmc.h"
+#include "av1/common/onyxc_int.h"
 #include "av1/common/pred_common.h"
 #include "av1/common/quant_common.h"
 #include "av1/common/reconinter.h"
@@ -12321,3 +12322,76 @@
     }
   }
 }
+
+static INLINE uint8_t get_pix(const uint8_t *src, int stride, int i, int j) {
+  return src[i + stride * j];
+}
+
+// 8-tap Gaussian convolution filter with sigma = 1.3, sums to 128,
+// all co-efficients must be even.
+DECLARE_ALIGNED(16, static const int16_t, gauss_filter[8]) = { 2,  12, 30, 40,
+                                                               30, 12, 2,  0 };
+
+void gaussian_blur(const uint8_t *src, int src_stride, int w, int h,
+                   uint8_t *dst) {
+  ConvolveParams conv_params = get_conv_params(0, 0, 0);
+  InterpFilterParams filter = { .filter_ptr = gauss_filter,
+                                .taps = 8,
+                                .subpel_shifts = 0,
+                                .interp_filter = EIGHTTAP_REGULAR };
+  av1_convolve_2d_sr(src, src_stride, dst, w, w, h, &filter, &filter, 0, 0,
+                     &conv_params);
+}
+
+/* Use standard 3x3 Sobel matrix. */
+sobel_xy sobel(const uint8_t *input, int stride, int i, int j) {
+  int16_t s_x = get_pix(input, stride, i - 1, j - 1) -
+                get_pix(input, stride, i + 1, j - 1) +
+                2 * get_pix(input, stride, i - 1, j) -
+                2 * get_pix(input, stride, i + 1, j) +
+                get_pix(input, stride, i - 1, j + 1) -
+                get_pix(input, stride, i + 1, j + 1);
+  int16_t s_y = get_pix(input, stride, i - 1, j - 1) +
+                2 * get_pix(input, stride, i, j - 1) +
+                get_pix(input, stride, i + 1, j - 1) -
+                get_pix(input, stride, i - 1, j + 1) -
+                2 * get_pix(input, stride, i, j + 1) -
+                get_pix(input, stride, i + 1, j + 1);
+  sobel_xy r = { .x = s_x, .y = s_y };
+  return r;
+}
+
+static uint16_t edge_probability(const uint8_t *input, int w, int h) {
+  // The probability of an edge in the whole image is the same as the highest
+  // probability of an edge for any individual pixel. Use Soebel as the metric
+  // for finding an edge.
+  uint16_t highest = 0;
+  // Ignore the 1 pixel border around the image for the computation.
+  for (int i = 1; i < w - 1; ++i) {
+    for (int j = 1; j < h - 1; ++j) {
+      sobel_xy g = sobel(input, w, i, j);
+      uint16_t magnitude = (uint16_t)sqrt(g.x * g.x + g.y * g.y);
+      highest = AOMMAX(highest, magnitude);
+    }
+  }
+  return highest;
+}
+
+/* Uses most of the Canny edge detection algorithm to find if there are any
+ * edges in the image.
+ */
+uint16_t av1_edge_exists(const uint8_t *src, int src_stride, int w, int h) {
+  if (w < 3 || h < 3) {
+    return 0;
+  }
+  uint8_t *blurred = NULL;
+  blurred = (uint8_t *)aom_memalign(32, sizeof(*blurred) * w * h);
+  gaussian_blur(src, src_stride, w, h, blurred);
+  // Skip the non-maximum suppression step in Canny edge detection. We just
+  // want a probability of an edge existing in the buffer, which is determined
+  // by the strongest edge in it -- we don't need to eliminate the weaker
+  // edges. Use Soebel for the edge detection.
+  uint16_t prob = edge_probability(blurred, w, h);
+  aom_free(blurred);
+  return prob;
+}
diff --git a/av1/encoder/rdopt.h b/av1/encoder/rdopt.h
index 4c11f90..d4df1e8 100644
--- a/av1/encoder/rdopt.h
+++ b/av1/encoder/rdopt.h
@@ -126,6 +126,33 @@
     struct macroblock *x, int mi_row, int mi_col, struct RD_STATS *rd_cost,
     BLOCK_SIZE bsize, PICK_MODE_CONTEXT *ctx, int64_t best_rd_so_far);
 
+/** Returns an integer indicating the strength of the edge.
+ * 0 means no edge found, 556 is the strength of a solid black/white edge,
+ * and the number may range higher if the signal is even stronger (e.g., on a
+ * corner).
+ */
+uint16_t av1_edge_exists(const uint8_t *src, int src_stride, int w, int h);
+
+/** Applies a Gaussian blur with sigma = 1.3. Used by av1_edge_exists and
+ * tests.
+ */
+void gaussian_blur(const uint8_t *src, int src_stride, int w, int h,
+                   uint8_t *dst);
+
+/* Applies standard 3x3 Sobel matrix. */
+typedef struct y4m_input y4m_input;
+
+/*The function used to perform chroma conversion.*/
+typedef void (*y4m_convert_func)(y4m_input *_y4m, unsigned char *_dst,
+                                 unsigned char *_src);
+
+typedef struct {
+  int16_t x;
+  int16_t y;
+} sobel_xy;
+
+sobel_xy sobel(const uint8_t *input, int stride, int i, int j);
+
 #if CONFIG_COLLECT_INTER_MODE_RD_STATS
 void av1_inter_mode_data_init(struct TileDataEnc *tile_data);
 void av1_inter_mode_data_fit(TileDataEnc *tile_data, int rdmult);
diff --git a/test/edge_detect_test.cc b/test/edge_detect_test.cc
new file mode 100644
index 0000000..6dd33a9
--- /dev/null
+++ b/test/edge_detect_test.cc
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2018, Alliance for Open Media. All rights reserved
+ *
+ * This source code is subject to the terms of the BSD 2 Clause License and
+ * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+ * was not distributed with this source code in the LICENSE file, you can
+ * obtain it at www.aomedia.org/license/software. If the Alliance for Open
+ * Media Patent License 1.0 was not distributed with this source code in the
+ * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+ */
+
+#include "aom_mem/aom_mem.h"
+#include "av1/encoder/rdopt.h"
+#include "third_party/googletest/src/googletest/include/gtest/gtest.h"
+
+namespace {
+
+class GaussianBlurTest : public ::testing::Test {};
+
+/** Get the (x, y) value from the input; if i or j is outside of the width
+ * or height, the nearest pixel value is returned.
+ */
+static uint8_t get_xy(const uint8_t *data, int w, int h, int i, int j) {
+  return data[AOMMAX(AOMMIN(i, w - 1), 0) + w * AOMMAX(AOMMIN(j, h - 1), 0)];
+}
+
+/** Given the image data, creates a new image with padded values, so an
+ * 8-tap filter can be convolved. The padded value is the same as the closest
+ * value in the image. Returns a pointer to the start of the image in the
+ * padded data. Must be freed with free_for_convolve.
+ */
+uint8_t *pad_8tap_convolve(const uint8_t *data, int w, int h) {
+  // AVX2 optimizations require the width to be a multiple of 8 and the height
+  // a multiple of 2.
+  assert(w % 8 == 0);
+  assert(h % 2 == 0);
+  // For an 8-tap filter, we need to pad with 3 lines on top and on the left,
+  // and 4 lines on the right and bottom, for 7 extra lines.
+  const int pad_w = w + 7;
+  const int pad_h = h + 7;
+  uint8_t *dst = (uint8_t *)aom_memalign(32, pad_w * pad_h);
+  // Fill in the data from the original.
+  for (int i = 0; i < pad_w; ++i) {
+    for (int j = 0; j < pad_h; ++j) {
+      dst[i + j * pad_w] = get_xy(data, w, h, i - 3, j - 3);
+    }
+  }
+  return dst + (w + 7) * 3 + 3;
+}
+
+static void free_pad_8tap(uint8_t *padded, int width) {
+  aom_free(padded - (width + 7) * 3 - 3);
+}
+
+static int stride_8tap(int width) { return width + 7; }
+
+TEST(GaussianBlurTest, UniformBrightness) {
+  // Generate images ranging in size from 8x8 to 32x32, with
+  // varying levels of brightness. In all cases, the algorithm should
+  // produce the same output.
+  // Note that width must increment in values of 8 for the AVX2 code to work.
+  for (int width = 8; width <= 32; width += 8) {
+    // Note that height must be even for the AVX2 code to work.
+    for (int height = 4; height <= 10; height += 2) {
+      for (int brightness = 0; brightness < 255; ++brightness) {
+        uint8_t *orig = (uint8_t *)malloc(width * height);
+        for (int i = 0; i < width * height; ++i) {
+          orig[i] = brightness;
+        }
+        uint8_t *padded = pad_8tap_convolve(orig, width, height);
+        free(orig);
+        uint8_t *output = (uint8_t *)aom_memalign(32, width * height);
+        gaussian_blur(padded, stride_8tap(width), width, height, output);
+        for (int i = 0; i < width * height; ++i) {
+          ASSERT_EQ(brightness, output[i]);
+        }
+        free_pad_8tap(padded, width);
+        aom_free(output);
+      }
+    }
+  }
+}
+
+TEST(GaussianBlurTest, SimpleExample) {
+  // Randomly generated 8x2.
+  const uint8_t luma[16] = { 241, 147, 7,  90,  184, 103, 28,  186,
+                             2,   248, 49, 242, 114, 146, 127, 22 };
+  uint8_t expected[] = { 151, 132, 117, 119, 124, 117, 109, 113,
+                         111, 124, 129, 135, 135, 122, 103, 88 };
+  const int w = 8;
+  const int h = 2;
+  uint8_t *padded = pad_8tap_convolve(luma, w, h);
+  uint8_t *output = (uint8_t *)aom_memalign(32, w * h);
+  gaussian_blur(padded, stride_8tap(w), w, h, output);
+
+  for (int i = 0; i < w * h; ++i) {
+    ASSERT_EQ(expected[i], output[i]);
+  }
+
+  free_pad_8tap(padded, w);
+  aom_free(output);
+}
+
+class EdgeDetectTest : public ::testing::Test {};
+
+TEST(EdgeDetectTest, UniformBrightness) {
+  // Generate images ranging in size from 8x2 to 32x32, with
+  // varying levels of brightness. In all cases, the algorithm should
+  // produce the same output.
+  for (int width = 8; width <= 32; width += 8) {
+    for (int height = 2; height <= 32; height += 2) {
+      for (int brightness = 0; brightness < 255; ++brightness) {
+        uint8_t *orig = (uint8_t *)malloc(width * height);
+        for (int i = 0; i < width * height; ++i) {
+          orig[i] = brightness;
+        }
+        uint8_t *padded = pad_8tap_convolve(orig, width, height);
+        free(orig);
+        ASSERT_EQ(0,
+                  av1_edge_exists(padded, stride_8tap(width), width, height));
+        free_pad_8tap(padded, width);
+      }
+    }
+  }
+}
+
+// Generate images ranging in size from 8x2 to 32x32, black on one side
+// and white on the other.
+TEST(EdgeDetectTest, BlackWhite) {
+  for (int width = 8; width <= 32; width += 8) {
+    for (int height = 2; height <= 32; height += 2) {
+      uint8_t *orig = (uint8_t *)malloc(width * height);
+      for (int i = 0; i < width; ++i) {
+        for (int j = 0; j < height; ++j) {
+          if (i < width / 2) {
+            orig[i + j * width] = 0;
+          } else {
+            orig[i + j * width] = 255;
+          }
+        }
+      }
+      uint8_t *padded = pad_8tap_convolve(orig, width, height);
+      free(orig);
+      if (height < 3) {
+        ASSERT_EQ(0,
+                  av1_edge_exists(padded, stride_8tap(width), width, height));
+      } else {
+        ASSERT_LE(556,
+                  av1_edge_exists(padded, stride_8tap(width), width, height));
+      }
+      free_pad_8tap(padded, width);
+    }
+  }
+}
+
+}  // namespace
diff --git a/test/test.cmake b/test/test.cmake
index b16ae14..513a0f1 100644
--- a/test/test.cmake
+++ b/test/test.cmake
@@ -179,6 +179,7 @@
               "${AOM_ROOT}/test/comp_avg_pred_test.cc"
               "${AOM_ROOT}/test/comp_avg_pred_test.h"
               "${AOM_ROOT}/test/comp_mask_variance_test.cc"
+              "${AOM_ROOT}/test/edge_detect_test.cc"
               "${AOM_ROOT}/test/encodetxb_test.cc"
               "${AOM_ROOT}/test/error_block_test.cc"
               "${AOM_ROOT}/test/fft_test.cc"