Changes to aos/vision/debug in preparation for the competition.

- blob_log-source: Support multiple key-commands for seeking to a target
 also added JustCheckForTarget which can be implemented to run faster
 for slow computer (will implement in followup).
- camera-source + debug_framework: Takes in camera parameters from the
caller.
- debug_window -> Scale text up on smaller monitors.
- overlay.h: Ben changed width to measure point to point on the cross.
  And added circles to the pixel-lines overlay.
- tcp-source: Drop packects when the computer can't keep up.
- debug_framework -> detect smaller monitors and scale down the viewport.

Change-Id: Iae65a0799231d006b38694a8a9cb3894e252033c
diff --git a/aos/vision/debug/blob_log-source.cc b/aos/vision/debug/blob_log-source.cc
index f2bfbb5..337a764 100644
--- a/aos/vision/debug/blob_log-source.cc
+++ b/aos/vision/debug/blob_log-source.cc
@@ -9,12 +9,28 @@
 #include <string>
 
 #include "aos/vision/blob/codec.h"
+#include "aos/vision/blob/stream_view.h"
+#include "aos/vision/debug/overlay.h"
 
 namespace aos {
 namespace vision {
 
 namespace {
 
+__attribute__((format(printf, 1, 2))) std::string SPrintf(const char *format,
+                                                          ...) {
+  va_list arglist;
+  va_start(arglist, format);
+  size_t count = vsnprintf(nullptr, 0, format, arglist);
+  va_end(arglist);
+
+  char out[count + 1];
+  va_start(arglist, format);
+  vsnprintf(out, count + 1, format, arglist);
+  va_end(arglist);
+  return std::string(&out[0], &out[count]);
+}
+
 long GetFileSize(const std::string &filename) {
   struct stat stat_buf;
   int rc = stat(filename.c_str(), &stat_buf);
@@ -45,7 +61,8 @@
   }
 
   bool ReadNext(BlobList *blob_list, ImageFormat *fmt, uint64_t *timestamp) {
-    if (buf_ - &tmp_buf_[0] >= len_) return false;
+    if (buf_ - &tmp_buf_[0] + sizeof(uint32_t) >= static_cast<size_t>(len_))
+      return false;
     if (prev_ != nullptr) prev_frames_.emplace_back(prev_);
     prev_ = buf_;
     DoRead(blob_list, fmt, timestamp);
@@ -56,7 +73,6 @@
     if (prev_frames_.empty()) return false;
     buf_ = prev_frames_.back();
     prev_frames_.pop_back();
-    buf_ += sizeof(uint32_t);
     DoRead(blob_list, fmt, timestamp);
     prev_ = nullptr;
     return true;
@@ -64,7 +80,11 @@
 
  private:
   void DoRead(BlobList *blob_list, ImageFormat *fmt, uint64_t *timestamp) {
-    buf_ += sizeof(uint32_t);
+    uint32_t size_delta = Int32Codec::Read(&buf_);
+    if (buf_ - &tmp_buf_[0] + size_delta > len_) {
+      fprintf(stderr, "Corrupted last record.\n");
+      exit(-1);
+    }
     *timestamp = Int64Codec::Read(&buf_);
     fmt->w = Int32Codec::Read(&buf_);
     fmt->h = Int32Codec::Read(&buf_);
@@ -132,6 +152,60 @@
   std::function<bool()> callback_;
 };
 
+class FrameStats {
+ public:
+  void UpdateStats(int64_t timestamp, bool has_target) {
+    if (has_target != last_has_target_) {
+      if (has_target && timestamp > last_timestamp_) {
+        ++frame_count_;
+      }
+      if (!has_target && timestamp < last_timestamp_) {
+        --frame_count_;
+      }
+    }
+    if (has_target && first_frame_timestamp_ == -1) {
+      first_frame_timestamp_ = timestamp;
+    }
+
+    last_timestamp_ = timestamp;
+    last_has_target_ = has_target;
+  }
+
+  void SetStartTimestamp(int64_t start_timestamp) {
+    start_timestamp_ = start_timestamp;
+  }
+
+  std::string Summary() const {
+    return SPrintf(" frame_count: %ld\n time_since_first_target: %7.3f",
+                   frame_count_,
+                   (last_timestamp_ - GetStartTimestamp()) / 1.0e9);
+  }
+
+  int64_t GetStartTimestamp() const {
+    if (first_frame_timestamp_ != -1) return first_frame_timestamp_;
+    return start_timestamp_;
+  }
+
+ private:
+  int64_t start_timestamp_;
+  int64_t frame_count_ = 0;
+  int64_t first_frame_timestamp_ = -1;
+  int64_t last_timestamp_ = -1;
+  bool last_has_target_ = 0;
+};
+
+// TODO: display this on the screen when they press help.
+const char *kHudText2 = &R"(
+commands:
+ h - display this message.
+ space - pause / unpause at normal speed (No extra mode).
+ s - Skip forward fast to the next target.
+ p - Skip backwards fast to the previous target.
+ left_arrow - single-step backwards.
+ right_arrow - single-step forwards.
+
+)"[1];
+
 class BlobLogImageSource : public ImageSource {
  public:
   void Init(const std::string &blob_log_filename,
@@ -144,26 +218,95 @@
     cb_.Reset(1000 / 25, [this]() { return Tick(); });
 
     frame_.ReadNext(image_source_.get());
+    start_timestamp_ = frame_.timestamp;
+    frame_stats_.SetStartTimestamp(start_timestamp_);
+
     interface_->NewBlobList(frame_.blob_list, frame_.fmt);
     interface_->InstallKeyPress([this](uint32_t keyval) {
       if (keyval == GDK_KEY_Left) {
-        frame_.ReadPrev(image_source_.get());
-        interface_->NewBlobList(frame_.blob_list, frame_.fmt);
+        ReadPrevFrame();
+        bool has_target = interface_->NewBlobList(frame_.blob_list, frame_.fmt);
+        frame_stats_.UpdateStats(frame_.timestamp, has_target);
       } else if (keyval == GDK_KEY_Right) {
-        frame_.ReadNext(image_source_.get());
-        interface_->NewBlobList(frame_.blob_list, frame_.fmt);
+        ReadNextFrame();
+        bool has_target = interface_->NewBlobList(frame_.blob_list, frame_.fmt);
+        frame_stats_.UpdateStats(frame_.timestamp, has_target);
+      } else if (keyval == GDK_KEY_space) {
+        if (mode_ != PAUSED) {
+          mode_ = PAUSED;
+        } else {
+          mode_ = NORMAL_MODE;
+        }
+      } else if (keyval == GDK_KEY_s) {
+        controller_.reset(new FastForwardUntilFrameController(this));
+        mode_ = FAST_MODE;
+      } else if (keyval == GDK_KEY_p) {
+        controller_.reset(new FastForwardUntilFrameController(this));
+        mode_ = FAST_MODE_REV;
       } else {
         return;
       }
     });
+    interface_->viewer()->AddOverlay(&overlay_);
+    overlay_.draw_fn = [this](RenderInterface *cr, double w, double h) {
+      (void)w;
+      (void)h;
+      cr->SetSourceRGB(1, 0, 0);
+      auto text = SPrintf(" time: %7.3f\n",
+                          (frame_.timestamp - start_timestamp_) / 1.0e9);
+      text += frame_stats_.Summary();
+      cr->Text(2, h - 100, 0, 0, text);
+    };
   }
 
   bool Tick() {
-    frame_.ReadNext(image_source_.get());
-    interface_->NewBlobList(frame_.blob_list, frame_.fmt);
+    if (need_timestamp_print_) {
+      fprintf(stderr, "time: %g\n",
+              (frame_.timestamp - start_timestamp_) / 1.0e9);
+      need_timestamp_print_ = false;
+    }
+    for (int i = 0; i < GetSpeed(); ++i) {
+      if (Direction()) {
+        ReadNextFrame();
+      } else {
+        ReadPrevFrame();
+      }
+      bool has_target =
+          interface_->JustCheckForTarget(frame_.blob_list, frame_.fmt);
+      frame_stats_.UpdateStats(frame_.timestamp, has_target);
+      if (controller_) {
+        controller_->NewFrame(has_target);
+      }
+      // Draw on the last frame:
+      if (i + 1 >= GetSpeed()) {
+        interface_->NewBlobList(frame_.blob_list, frame_.fmt);
+      }
+    }
     return true;
   }
 
+  int GetSpeed() {
+    if (mode_ == PAUSED) return 0;
+    if (mode_ == NORMAL_MODE) return 1;
+    if (mode_ == FAST_MODE || mode_ == FAST_MODE_REV) return 60;
+    return 0;
+  }
+
+  bool Direction() {
+    if (mode_ == FAST_MODE_REV) return false;
+    return true;
+  }
+
+  void ReadNextFrame() {
+    frame_.ReadNext(image_source_.get());
+    need_timestamp_print_ = true;
+  }
+
+  void ReadPrevFrame() {
+    frame_.ReadPrev(image_source_.get());
+    need_timestamp_print_ = true;
+  }
+
   const char *GetHelpMessage() override {
     return &R"(
     format_spec is the name of a file in blob list format.
@@ -172,10 +315,50 @@
   }
 
  private:
+  bool need_timestamp_print_ = true;
+  uint64_t start_timestamp_ = 0;
+
+  class Controller {
+   public:
+    virtual ~Controller() {}
+    virtual void NewFrame(bool has_target) = 0;
+  };
+
+  class FastForwardUntilFrameController : public Controller {
+   public:
+    FastForwardUntilFrameController(BlobLogImageSource *proxy)
+        : proxy_(proxy) {}
+
+    void NewFrame(bool has_target) override {
+      if (!has_target) inside_target = false;
+      if (!inside_target && has_target) {
+        proxy_->mode_ = PAUSED;
+        proxy_->controller_.reset(nullptr);
+      }
+    }
+
+    BlobLogImageSource *proxy_;
+    bool inside_target = true;
+  };
+
+  std::unique_ptr<Controller> controller_;
+
+  FrameStats frame_stats_;
+
+  enum Mode {
+    PAUSED,
+    NORMAL_MODE,
+    FAST_MODE,
+    FAST_MODE_REV,
+  };
+  Mode mode_ = PAUSED;
+
+  // LambdaOverlay text_overlay_;
   TimeoutCallback cb_;
   DebugFrameworkInterface *interface_ = nullptr;
   std::unique_ptr<InputFile> image_source_;
   BlobStreamFrame frame_;
+  LambdaOverlay overlay_;
 };
 
 REGISTER_IMAGE_SOURCE("blob_log", BlobLogImageSource);