Make the fast thresholding work

The only functional change was removing the block which did the same
algorithm non-flattened. It has tests now to verify it does the same
thing as the slow version.

Change-Id: I63da1a6374c2adfabf764b62d5c33f91897ce2a7
diff --git a/aos/vision/blob/BUILD b/aos/vision/blob/BUILD
index 2e77079..81afb93 100644
--- a/aos/vision/blob/BUILD
+++ b/aos/vision/blob/BUILD
@@ -45,6 +45,7 @@
     ],
     deps = [
         ":range_image",
+        "//aos/logging",
         "//aos/vision/image:image_types",
     ],
 )
diff --git a/aos/vision/blob/threshold.cc b/aos/vision/blob/threshold.cc
index 4fc58eb..74809d1 100644
--- a/aos/vision/blob/threshold.cc
+++ b/aos/vision/blob/threshold.cc
@@ -1,210 +1,230 @@
 #include "aos/vision/blob/threshold.h"
 
+#include "aos/logging/logging.h"
+
 namespace aos {
 namespace vision {
 
+// Expands to a unique value for each combination of values for 5 bools.
 #define MASH(v0, v1, v2, v3, v4)                                  \
   ((uint8_t(v0) << 4) | (uint8_t(v1) << 3) | (uint8_t(v2) << 2) | \
    (uint8_t(v3) << 1) | (uint8_t(v4)))
 
+// At a high level, the algorithm is the same as the slow thresholding, except
+// it operates in 4-pixel chunks. The handling for each of these chunks is
+// manually flattened (via codegen) into a 32-case switch statement. There are
+// 2^4 cases for each pixel being in or out, along with another set of cases
+// depending on whether the start of the chunk is in a range or not.
 RangeImage FastYuyvYThreshold(ImageFormat fmt, const char *data,
                               uint8_t value) {
-  std::vector<std::vector<ImageRange>> ranges;
-  ranges.reserve(fmt.h);
+  CHECK_EQ(0, fmt.w % 4);
+  std::vector<std::vector<ImageRange>> result;
+  result.reserve(fmt.h);
+
+  // Iterate through each row.
   for (int y = 0; y < fmt.h; ++y) {
-    const char *row = fmt.w * y * 2 + data;
-    bool p_score = false;
-    int pstart = -1;
-    std::vector<ImageRange> rngs;
+    // The start of the data for the current row.
+    const char *const current_row = fmt.w * y * 2 + data;
+    bool in_range = false;
+    int current_range_start = -1;
+    std::vector<ImageRange> current_row_ranges;
+    // Iterate through each 4-pixel chunk
     for (int x = 0; x < fmt.w / 4; ++x) {
-      uint8_t v[8];
-      memcpy(&v[0], row + x * 4 * 2, 8);
-      uint8_t pattern =
-          MASH(p_score, v[0] > value, v[2] > value, v[4] > value, v[6] > value);
+      // The per-channel (YUYV) values in the current chunk.
+      uint8_t chunk_channels[8];
+      memcpy(&chunk_channels[0], current_row + x * 4 * 2, 8);
+      const uint8_t pattern =
+          MASH(in_range, chunk_channels[0] > value, chunk_channels[2] > value,
+               chunk_channels[4] > value, chunk_channels[6] > value);
       switch (pattern) {
-        /*
+        // clang-format off
+/*
 # Ruby code to generate the below code:
 32.times do |v|
         puts "case MASH(#{[v[4], v[3], v[2], v[1], v[0]].join(", ")}):"
-        p_score = v[4]
-        pstart = "pstart"
+        in_range = v[4]
+        current_range_start = "current_range_start"
         4.times do |i|
-                if v[3 - i] != p_score
-                        if (p_score == 1)
-                                puts "  rngs.emplace_back(ImageRange(#{pstart},
-x * 4 + #{i}));"
+                if v[3 - i] != in_range
+                        if (in_range == 1)
+                                puts "  current_row_ranges.emplace_back(ImageRange(#{current_range_start}, x * 4 + #{i}));"
                         else
-                                pstart = "x * 4 + #{i}"
+                                current_range_start = "x * 4 + #{i}"
                         end
-                        p_score = v[3 - i]
+                        in_range = v[3 - i]
                 end
         end
-        if (pstart != "pstart")
-                puts "  pstart = #{pstart};"
+        if (current_range_start != "current_range_start")
+                puts "  current_range_start = #{current_range_start};"
         end
-        if (p_score != v[4])
-                puts "  p_score = #{["false", "true"][v[0]]};"
+        if (in_range != v[4])
+                puts "  in_range = #{["false", "true"][v[0]]};"
         end
         puts "  break;"
 end
 */
+        // clang-format on
         case MASH(0, 0, 0, 0, 0):
           break;
         case MASH(0, 0, 0, 0, 1):
-          pstart = x * 4 + 3;
-          p_score = true;
+          current_range_start = x * 4 + 3;
+          in_range = true;
           break;
         case MASH(0, 0, 0, 1, 0):
-          rngs.emplace_back(ImageRange(x * 4 + 2, x * 4 + 3));
-          pstart = x * 4 + 2;
+          current_row_ranges.emplace_back(ImageRange(x * 4 + 2, x * 4 + 3));
+          current_range_start = x * 4 + 2;
           break;
         case MASH(0, 0, 0, 1, 1):
-          pstart = x * 4 + 2;
-          p_score = true;
+          current_range_start = x * 4 + 2;
+          in_range = true;
           break;
         case MASH(0, 0, 1, 0, 0):
-          rngs.emplace_back(ImageRange(x * 4 + 1, x * 4 + 2));
-          pstart = x * 4 + 1;
+          current_row_ranges.emplace_back(ImageRange(x * 4 + 1, x * 4 + 2));
+          current_range_start = x * 4 + 1;
           break;
         case MASH(0, 0, 1, 0, 1):
-          rngs.emplace_back(ImageRange(x * 4 + 1, x * 4 + 2));
-          pstart = x * 4 + 3;
-          p_score = true;
+          current_row_ranges.emplace_back(ImageRange(x * 4 + 1, x * 4 + 2));
+          current_range_start = x * 4 + 3;
+          in_range = true;
           break;
         case MASH(0, 0, 1, 1, 0):
-          rngs.emplace_back(ImageRange(x * 4 + 1, x * 4 + 3));
-          pstart = x * 4 + 1;
+          current_row_ranges.emplace_back(ImageRange(x * 4 + 1, x * 4 + 3));
+          current_range_start = x * 4 + 1;
           break;
         case MASH(0, 0, 1, 1, 1):
-          pstart = x * 4 + 1;
-          p_score = true;
+          current_range_start = x * 4 + 1;
+          in_range = true;
           break;
         case MASH(0, 1, 0, 0, 0):
-          rngs.emplace_back(ImageRange(x * 4 + 0, x * 4 + 1));
-          pstart = x * 4 + 0;
+          current_row_ranges.emplace_back(ImageRange(x * 4 + 0, x * 4 + 1));
+          current_range_start = x * 4 + 0;
           break;
         case MASH(0, 1, 0, 0, 1):
-          rngs.emplace_back(ImageRange(x * 4 + 0, x * 4 + 1));
-          pstart = x * 4 + 3;
-          p_score = true;
+          current_row_ranges.emplace_back(ImageRange(x * 4 + 0, x * 4 + 1));
+          current_range_start = x * 4 + 3;
+          in_range = true;
           break;
         case MASH(0, 1, 0, 1, 0):
-          rngs.emplace_back(ImageRange(x * 4 + 0, x * 4 + 1));
-          rngs.emplace_back(ImageRange(x * 4 + 2, x * 4 + 3));
-          pstart = x * 4 + 2;
+          current_row_ranges.emplace_back(ImageRange(x * 4 + 0, x * 4 + 1));
+          current_row_ranges.emplace_back(ImageRange(x * 4 + 2, x * 4 + 3));
+          current_range_start = x * 4 + 2;
           break;
         case MASH(0, 1, 0, 1, 1):
-          rngs.emplace_back(ImageRange(x * 4 + 0, x * 4 + 1));
-          pstart = x * 4 + 2;
-          p_score = true;
+          current_row_ranges.emplace_back(ImageRange(x * 4 + 0, x * 4 + 1));
+          current_range_start = x * 4 + 2;
+          in_range = true;
           break;
         case MASH(0, 1, 1, 0, 0):
-          rngs.emplace_back(ImageRange(x * 4 + 0, x * 4 + 2));
-          pstart = x * 4 + 0;
+          current_row_ranges.emplace_back(ImageRange(x * 4 + 0, x * 4 + 2));
+          current_range_start = x * 4 + 0;
           break;
         case MASH(0, 1, 1, 0, 1):
-          rngs.emplace_back(ImageRange(x * 4 + 0, x * 4 + 2));
-          pstart = x * 4 + 3;
-          p_score = true;
+          current_row_ranges.emplace_back(ImageRange(x * 4 + 0, x * 4 + 2));
+          current_range_start = x * 4 + 3;
+          in_range = true;
           break;
         case MASH(0, 1, 1, 1, 0):
-          rngs.emplace_back(ImageRange(x * 4 + 0, x * 4 + 3));
-          pstart = x * 4 + 0;
+          current_row_ranges.emplace_back(ImageRange(x * 4 + 0, x * 4 + 3));
+          current_range_start = x * 4 + 0;
           break;
         case MASH(0, 1, 1, 1, 1):
-          pstart = x * 4 + 0;
-          p_score = true;
+          current_range_start = x * 4 + 0;
+          in_range = true;
           break;
         case MASH(1, 0, 0, 0, 0):
-          rngs.emplace_back(ImageRange(pstart, x * 4 + 0));
-          p_score = false;
+          current_row_ranges.emplace_back(
+              ImageRange(current_range_start, x * 4 + 0));
+          in_range = false;
           break;
         case MASH(1, 0, 0, 0, 1):
-          rngs.emplace_back(ImageRange(pstart, x * 4 + 0));
-          pstart = x * 4 + 3;
+          current_row_ranges.emplace_back(
+              ImageRange(current_range_start, x * 4 + 0));
+          current_range_start = x * 4 + 3;
           break;
         case MASH(1, 0, 0, 1, 0):
-          rngs.emplace_back(ImageRange(pstart, x * 4 + 0));
-          rngs.emplace_back(ImageRange(x * 4 + 2, x * 4 + 3));
-          pstart = x * 4 + 2;
-          p_score = false;
+          current_row_ranges.emplace_back(
+              ImageRange(current_range_start, x * 4 + 0));
+          current_row_ranges.emplace_back(ImageRange(x * 4 + 2, x * 4 + 3));
+          current_range_start = x * 4 + 2;
+          in_range = false;
           break;
         case MASH(1, 0, 0, 1, 1):
-          rngs.emplace_back(ImageRange(pstart, x * 4 + 0));
-          pstart = x * 4 + 2;
+          current_row_ranges.emplace_back(
+              ImageRange(current_range_start, x * 4 + 0));
+          current_range_start = x * 4 + 2;
           break;
         case MASH(1, 0, 1, 0, 0):
-          rngs.emplace_back(ImageRange(pstart, x * 4 + 0));
-          rngs.emplace_back(ImageRange(x * 4 + 1, x * 4 + 2));
-          pstart = x * 4 + 1;
-          p_score = false;
+          current_row_ranges.emplace_back(
+              ImageRange(current_range_start, x * 4 + 0));
+          current_row_ranges.emplace_back(ImageRange(x * 4 + 1, x * 4 + 2));
+          current_range_start = x * 4 + 1;
+          in_range = false;
           break;
         case MASH(1, 0, 1, 0, 1):
-          rngs.emplace_back(ImageRange(pstart, x * 4 + 0));
-          rngs.emplace_back(ImageRange(x * 4 + 1, x * 4 + 2));
-          pstart = x * 4 + 3;
+          current_row_ranges.emplace_back(
+              ImageRange(current_range_start, x * 4 + 0));
+          current_row_ranges.emplace_back(ImageRange(x * 4 + 1, x * 4 + 2));
+          current_range_start = x * 4 + 3;
           break;
         case MASH(1, 0, 1, 1, 0):
-          rngs.emplace_back(ImageRange(pstart, x * 4 + 0));
-          rngs.emplace_back(ImageRange(x * 4 + 1, x * 4 + 3));
-          pstart = x * 4 + 1;
-          p_score = false;
+          current_row_ranges.emplace_back(
+              ImageRange(current_range_start, x * 4 + 0));
+          current_row_ranges.emplace_back(ImageRange(x * 4 + 1, x * 4 + 3));
+          current_range_start = x * 4 + 1;
+          in_range = false;
           break;
         case MASH(1, 0, 1, 1, 1):
-          rngs.emplace_back(ImageRange(pstart, x * 4 + 0));
-          pstart = x * 4 + 1;
+          current_row_ranges.emplace_back(
+              ImageRange(current_range_start, x * 4 + 0));
+          current_range_start = x * 4 + 1;
           break;
         case MASH(1, 1, 0, 0, 0):
-          rngs.emplace_back(ImageRange(pstart, x * 4 + 1));
-          p_score = false;
+          current_row_ranges.emplace_back(
+              ImageRange(current_range_start, x * 4 + 1));
+          in_range = false;
           break;
         case MASH(1, 1, 0, 0, 1):
-          rngs.emplace_back(ImageRange(pstart, x * 4 + 1));
-          pstart = x * 4 + 3;
+          current_row_ranges.emplace_back(
+              ImageRange(current_range_start, x * 4 + 1));
+          current_range_start = x * 4 + 3;
           break;
         case MASH(1, 1, 0, 1, 0):
-          rngs.emplace_back(ImageRange(pstart, x * 4 + 1));
-          rngs.emplace_back(ImageRange(x * 4 + 2, x * 4 + 3));
-          pstart = x * 4 + 2;
-          p_score = false;
+          current_row_ranges.emplace_back(
+              ImageRange(current_range_start, x * 4 + 1));
+          current_row_ranges.emplace_back(ImageRange(x * 4 + 2, x * 4 + 3));
+          current_range_start = x * 4 + 2;
+          in_range = false;
           break;
         case MASH(1, 1, 0, 1, 1):
-          rngs.emplace_back(ImageRange(pstart, x * 4 + 1));
-          pstart = x * 4 + 2;
+          current_row_ranges.emplace_back(
+              ImageRange(current_range_start, x * 4 + 1));
+          current_range_start = x * 4 + 2;
           break;
         case MASH(1, 1, 1, 0, 0):
-          rngs.emplace_back(ImageRange(pstart, x * 4 + 2));
-          p_score = false;
+          current_row_ranges.emplace_back(
+              ImageRange(current_range_start, x * 4 + 2));
+          in_range = false;
           break;
         case MASH(1, 1, 1, 0, 1):
-          rngs.emplace_back(ImageRange(pstart, x * 4 + 2));
-          pstart = x * 4 + 3;
+          current_row_ranges.emplace_back(
+              ImageRange(current_range_start, x * 4 + 2));
+          current_range_start = x * 4 + 3;
           break;
         case MASH(1, 1, 1, 1, 0):
-          rngs.emplace_back(ImageRange(pstart, x * 4 + 3));
-          p_score = false;
+          current_row_ranges.emplace_back(
+              ImageRange(current_range_start, x * 4 + 3));
+          in_range = false;
           break;
         case MASH(1, 1, 1, 1, 1):
           break;
       }
-
-      for (int i = 0; i < 4; ++i) {
-        if ((v[i * 2] > value) != p_score) {
-          if (p_score) {
-            rngs.emplace_back(ImageRange(pstart, x * 4 + i));
-          } else {
-            pstart = x * 4 + i;
-          }
-          p_score = !p_score;
-        }
-      }
     }
-    if (p_score) {
-      rngs.emplace_back(ImageRange(pstart, fmt.w));
+    if (in_range) {
+      current_row_ranges.emplace_back(ImageRange(current_range_start, fmt.w));
     }
-    ranges.push_back(rngs);
+    result.push_back(current_row_ranges);
   }
-  return RangeImage(0, std::move(ranges));
+  return RangeImage(0, std::move(result));
 }
 
 #undef MASH
diff --git a/aos/vision/blob/threshold.h b/aos/vision/blob/threshold.h
index 441a058..9891722 100644
--- a/aos/vision/blob/threshold.h
+++ b/aos/vision/blob/threshold.h
@@ -17,28 +17,31 @@
   static_assert(
       std::is_convertible<PointTestFn, std::function<bool(int, int)>>::value,
       "Invalid threshold function");
-  std::vector<std::vector<ImageRange>> ranges;
-  ranges.reserve(fmt.h);
+  std::vector<std::vector<ImageRange>> result;
+  result.reserve(fmt.h);
+  // Iterate through each row.
   for (int y = 0; y < fmt.h; ++y) {
-    bool p_score = false;
-    int pstart = -1;
-    std::vector<ImageRange> rngs;
+    // Whether we're currently in a range.
+    bool in_range = false;
+    int current_range_start = -1;
+    std::vector<ImageRange> current_row_ranges;
+    // Iterate through each pixel.
     for (int x = 0; x < fmt.w; ++x) {
-      if (fn(x, y) != p_score) {
-        if (p_score) {
-          rngs.emplace_back(ImageRange(pstart, x));
+      if (fn(x, y) != in_range) {
+        if (in_range) {
+          current_row_ranges.emplace_back(ImageRange(current_range_start, x));
         } else {
-          pstart = x;
+          current_range_start = x;
         }
-        p_score = !p_score;
+        in_range = !in_range;
       }
     }
-    if (p_score) {
-      rngs.emplace_back(ImageRange(pstart, fmt.w));
+    if (in_range) {
+      current_row_ranges.emplace_back(ImageRange(current_range_start, fmt.w));
     }
-    ranges.push_back(rngs);
+    result.push_back(current_row_ranges);
   }
-  return RangeImage(0, std::move(ranges));
+  return RangeImage(0, std::move(result));
 }
 
 }  // namespace threshold_internal
@@ -72,7 +75,7 @@
 }
 
 // Thresholds an image in YUYV format, selecting pixels with a Y (luma) greater
-// than value.
+// than value. The width must be a multiple of 4.
 //
 // This is implemented via some tricky bit shuffling that goes fast.
 RangeImage FastYuyvYThreshold(ImageFormat fmt, const char *data, uint8_t value);
diff --git a/aos/vision/blob/threshold_test.cc b/aos/vision/blob/threshold_test.cc
index 96a2a22..108da35 100644
--- a/aos/vision/blob/threshold_test.cc
+++ b/aos/vision/blob/threshold_test.cc
@@ -1,5 +1,6 @@
 #include "aos/vision/blob/threshold.h"
 
+#include <random>
 #include <vector>
 
 #include "aos/vision/blob/range_image.h"
@@ -12,87 +13,179 @@
 namespace testing {
 
 class YuyvYThresholdTest : public ::testing::Test {
+ public:
+  std::vector<char> RandomImage(ImageFormat format) {
+    std::vector<char> result;
+    result.resize(format.w * format.h * 2);
+    std::uniform_int_distribution<char> distribution(
+        std::numeric_limits<char>::min(), std::numeric_limits<char>::max());
+    for (size_t i = 0; i < result.size(); ++i) {
+      result[i] = distribution(generator_);
+    }
+    return result;
+  }
+
+ private:
+  std::minstd_rand generator_;
 };
 
 // Verifies that a simple image is thresholded correctly.
 //
 // Specifically, we want to get this result from the thresholding:
-//  --+--
-//  +---+
-//  -+++-
-//  +++-+
-//  -----
-//  ++-++
-//  +++++
-//  +-+-+
+//  --+-----
+//  +------+
+//  -++++++-
+//  +++-++++
+//  --------
+//  ++++-+++
+//  ++++++++
+//  +-+-+--+
 TEST_F(YuyvYThresholdTest, SimpleImage) {
   ImageFormat format;
-  format.w = 5;
+  format.w = 8;
   format.h = 8;
 
   std::vector<std::vector<ImageRange>> expected_ranges;
   std::vector<char> image;
-  image.resize(5 * 8 * 2);
-  //  --+--
-  image[0 * 2 + 0 * 10] = 0;
-  image[1 * 2 + 0 * 10] = 0;
-  image[2 * 2 + 0 * 10] = 128;
-  image[3 * 2 + 0 * 10] = 127;
-  image[4 * 2 + 0 * 10] = 0;
+  image.resize(8 * 8 * 2);
+  //  --+-----
+  image[0 * 2 + 0 * 16] = 0;
+  image[1 * 2 + 0 * 16] = 0;
+  image[2 * 2 + 0 * 16] = 128;
+  image[3 * 2 + 0 * 16] = 127;
+  image[4 * 2 + 0 * 16] = 0;
+  image[5 * 2 + 0 * 16] = 0;
+  image[6 * 2 + 0 * 16] = 0;
+  image[7 * 2 + 0 * 16] = 0;
   expected_ranges.push_back({{{2, 3}}});
-  //  +---+
-  image[0 * 2 + 1 * 10] = 128;
-  image[1 * 2 + 1 * 10] = 0;
-  image[2 * 2 + 1 * 10] = 0;
-  image[3 * 2 + 1 * 10] = 10;
-  image[4 * 2 + 1 * 10] = 255;
-  expected_ranges.push_back({{{0, 1}, {4, 5}}});
-  //  -+++-
-  image[0 * 2 + 2 * 10] = 73;
-  image[1 * 2 + 2 * 10] = 250;
-  image[2 * 2 + 2 * 10] = 251;
-  image[3 * 2 + 2 * 10] = 252;
-  image[4 * 2 + 2 * 10] = 45;
-  expected_ranges.push_back({{{1, 4}}});
-  //  +++-+
-  image[0 * 2 + 3 * 10] = 128;
-  image[1 * 2 + 3 * 10] = 134;
-  image[2 * 2 + 3 * 10] = 250;
-  image[3 * 2 + 3 * 10] = 0;
-  image[4 * 2 + 3 * 10] = 230;
-  expected_ranges.push_back({{{0, 3}, {4, 5}}});
-  //  -----
-  image[0 * 2 + 4 * 10] = 7;
-  image[1 * 2 + 4 * 10] = 120;
-  image[2 * 2 + 4 * 10] = 127;
-  image[3 * 2 + 4 * 10] = 0;
-  image[4 * 2 + 4 * 10] = 50;
+  //  +------+
+  image[0 * 2 + 1 * 16] = 128;
+  image[1 * 2 + 1 * 16] = 0;
+  image[2 * 2 + 1 * 16] = 0;
+  image[3 * 2 + 1 * 16] = 10;
+  image[4 * 2 + 1 * 16] = 30;
+  image[5 * 2 + 1 * 16] = 50;
+  image[6 * 2 + 1 * 16] = 70;
+  image[7 * 2 + 1 * 16] = 255;
+  expected_ranges.push_back({{{0, 1}, {7, 8}}});
+  //  -++++++-
+  image[0 * 2 + 2 * 16] = 73;
+  image[1 * 2 + 2 * 16] = 246;
+  image[2 * 2 + 2 * 16] = 247;
+  image[3 * 2 + 2 * 16] = 248;
+  image[4 * 2 + 2 * 16] = 249;
+  image[5 * 2 + 2 * 16] = 250;
+  image[6 * 2 + 2 * 16] = 250;
+  image[7 * 2 + 2 * 16] = 45;
+  expected_ranges.push_back({{{1, 7}}});
+  //  +++-++++
+  image[0 * 2 + 3 * 16] = 128;
+  image[1 * 2 + 3 * 16] = 134;
+  image[2 * 2 + 3 * 16] = 250;
+  image[3 * 2 + 3 * 16] = 0;
+  image[4 * 2 + 3 * 16] = 230;
+  image[5 * 2 + 3 * 16] = 230;
+  image[6 * 2 + 3 * 16] = 230;
+  image[7 * 2 + 3 * 16] = 210;
+  expected_ranges.push_back({{{0, 3}, {4, 8}}});
+  //  --------
+  image[0 * 2 + 4 * 16] = 7;
+  image[1 * 2 + 4 * 16] = 120;
+  image[2 * 2 + 4 * 16] = 127;
+  image[3 * 2 + 4 * 16] = 0;
+  image[4 * 2 + 4 * 16] = 50;
+  image[5 * 2 + 4 * 16] = 80;
+  image[6 * 2 + 4 * 16] = 110;
+  image[7 * 2 + 4 * 16] = 25;
   expected_ranges.push_back({{}});
-  //  ++-++
-  image[0 * 2 + 5 * 10] = 140;
-  image[1 * 2 + 5 * 10] = 140;
-  image[2 * 2 + 5 * 10] = 0;
-  image[3 * 2 + 5 * 10] = 140;
-  image[4 * 2 + 5 * 10] = 140;
-  expected_ranges.push_back({{{0, 2}, {3, 5}}});
-  //  +++++
-  image[0 * 2 + 6 * 10] = 128;
-  image[1 * 2 + 6 * 10] = 128;
-  image[2 * 2 + 6 * 10] = 128;
-  image[3 * 2 + 6 * 10] = 128;
-  image[4 * 2 + 6 * 10] = 128;
-  expected_ranges.push_back({{{0, 5}}});
-  //  +-+-+
-  image[0 * 2 + 7 * 10] = 200;
-  image[1 * 2 + 7 * 10] = 0;
-  image[2 * 2 + 7 * 10] = 200;
-  image[3 * 2 + 7 * 10] = 0;
-  image[4 * 2 + 7 * 10] = 200;
-  expected_ranges.push_back({{{0, 1}, {2, 3}, {4, 5}}});
+  //  ++++-+++
+  image[0 * 2 + 5 * 16] = 140;
+  image[1 * 2 + 5 * 16] = 140;
+  image[2 * 2 + 5 * 16] = 140;
+  image[3 * 2 + 5 * 16] = 140;
+  image[4 * 2 + 5 * 16] = 0;
+  image[5 * 2 + 5 * 16] = 140;
+  image[6 * 2 + 5 * 16] = 140;
+  image[7 * 2 + 5 * 16] = 140;
+  expected_ranges.push_back({{{0, 4}, {5, 8}}});
+  //  ++++++++
+  image[0 * 2 + 6 * 16] = 128;
+  image[1 * 2 + 6 * 16] = 128;
+  image[2 * 2 + 6 * 16] = 128;
+  image[3 * 2 + 6 * 16] = 128;
+  image[4 * 2 + 6 * 16] = 128;
+  image[5 * 2 + 6 * 16] = 128;
+  image[6 * 2 + 6 * 16] = 128;
+  image[7 * 2 + 6 * 16] = 128;
+  expected_ranges.push_back({{{0, 8}}});
+  //  +-+-+--+
+  image[0 * 2 + 7 * 16] = 200;
+  image[1 * 2 + 7 * 16] = 0;
+  image[2 * 2 + 7 * 16] = 200;
+  image[3 * 2 + 7 * 16] = 0;
+  image[4 * 2 + 7 * 16] = 200;
+  image[5 * 2 + 7 * 16] = 0;
+  image[6 * 2 + 7 * 16] = 0;
+  image[7 * 2 + 7 * 16] = 200;
+  expected_ranges.push_back({{{0, 1}, {2, 3}, {4, 5}, {7, 8}}});
   const RangeImage expected_result(0, std::move(expected_ranges));
 
   const auto slow_result = SlowYuyvYThreshold(format, image.data(), 127);
   ASSERT_EQ(expected_result, slow_result);
+  const auto fast_result = FastYuyvYThreshold(format, image.data(), 127);
+  ASSERT_EQ(expected_result, fast_result);
+}
+
+// Verifies that a couple of completely random images match.
+TEST_F(YuyvYThresholdTest, Random) {
+  for (int i = 0; i < 10; ++i) {
+    ImageFormat small_format;
+    small_format.w = 16;
+    small_format.h = 16;
+    const auto small_image = RandomImage(small_format);
+    const auto slow_result =
+        SlowYuyvYThreshold(small_format, small_image.data(), 127);
+    const auto fast_result =
+        FastYuyvYThreshold(small_format, small_image.data(), 127);
+    ASSERT_EQ(slow_result, fast_result);
+  }
+  for (int i = 0; i < 10; ++i) {
+    ImageFormat large_format;
+    large_format.w = 1024;
+    large_format.h = 512;
+    const auto large_image = RandomImage(large_format);
+    const auto slow_result =
+        SlowYuyvYThreshold(large_format, large_image.data(), 127);
+    const auto fast_result =
+        FastYuyvYThreshold(large_format, large_image.data(), 127);
+    ASSERT_EQ(slow_result, fast_result);
+  }
+}
+
+// Verifies that changing the U and V values doesn't affect the result.
+TEST_F(YuyvYThresholdTest, UVIgnored) {
+  ImageFormat format;
+  format.w = 32;
+  format.h = 20;
+  const auto baseline_image = RandomImage(format);
+  const auto baseline_result =
+      SlowYuyvYThreshold(format, baseline_image.data(), 127);
+  for (int i = 0; i < 5; ++i) {
+    auto tweaked_image = RandomImage(format);
+    for (int y = 0; y < format.h; ++y) {
+      for (int x = 0; x < format.w; ++x) {
+        tweaked_image[x * 2 + y * format.w * 2] =
+            baseline_image[x * 2 + y * format.w * 2];
+      }
+    }
+
+    const auto slow_result =
+        SlowYuyvYThreshold(format, tweaked_image.data(), 127);
+    ASSERT_EQ(baseline_result, slow_result);
+    const auto fast_result =
+        FastYuyvYThreshold(format, tweaked_image.data(), 127);
+    ASSERT_EQ(baseline_result, fast_result);
+  }
 }
 
 }  // namespace testing