Initial commit to draw keypoints on the image.

Change-Id: Ia50f64d8a131ee9ab455ebe2700714a2edea91d1
diff --git a/y2020/www/image_handler.ts b/y2020/www/image_handler.ts
index 7bb2cfb..802e448 100644
--- a/y2020/www/image_handler.ts
+++ b/y2020/www/image_handler.ts
@@ -2,21 +2,26 @@
 
 export class ImageHandler {
   private canvas = document.createElement('canvas');
+  private imageBuffer: Uint8ClampedArray|null = null;
+  private imageTimestamp: flatbuffers.Long|null = null;
+  private result: fr971.vision.ImageMatchResult|null = null;
+  private resultTimestamp: flatbuffers.Long|null = null;
 
   constructor() {
     document.body.appendChild(this.canvas);
   }
 
-  handleImage(data: Uint8Array) {
+  handleImage(data: Uint8Array): void {
     const fbBuffer = new flatbuffers.ByteBuffer(data);
     const image = CameraImage.getRootAsCameraImage(fbBuffer);
+    this.imageTimestamp = image.monotonicTimestampNs();
 
     const width = image.cols();
     const height = image.rows();
     if (width === 0 || height === 0) {
       return;
     }
-    const imageBuffer = new Uint8ClampedArray(width * height * 4); // RGBA
+    this.imageBuffer = new Uint8ClampedArray(width * height * 4); // RGBA
 
     // Read four bytes (YUYV) from the data and transform into two pixels of
     // RGBA for canvas
@@ -46,16 +51,47 @@
       }
     }
 
+    draw();
+  }
+
+  handleImageMetadata(data: Uint8Array): void {
+    const fbBuffer = new flatbuffers.ByteBuffer(data);
+    this.result = frc971.vision.ImageMatchResult.getRootAsImageMatchResult(fbBuffer);
+    this.resultTimestamp = result.imageMonotonicTimestampNs();
+    draw();
+  }
+
+  draw(): void {
+    if (imageTimestamp.low !== resultTimestamp.low ||
+        imageTimestamp.high !== resultTimestamp.high) {
+      return;
+    }
     const ctx = this.canvas.getContext('2d');
 
     this.canvas.width = width;
     this.canvas.height = height;
     const idata = ctx.createImageData(width, height);
-    idata.data.set(imageBuffer);
+    idata.data.set(this.imageBuffer);
     ctx.putImageData(idata, 0, 0);
+    ctx.beginPath();
+    for (const feature of this.result.getFeatures()) {
+      // Based on OpenCV drawKeypoint.
+      ctx.arc(feature.x, feature.y, feature.size, 0, 2 * Math.PI);
+      ctx.moveTo(feature.x, feature.y);
+      // TODO(alex): check that angle is correct (0?, direction?)
+      const angle = feature.angle * Math.PI / 180;
+      ctx.lineTo(
+          feature.x + feature.radius * cos(angle),
+          feature.y + feature.radius * sin(angle));
+    }
+    ctx.stroke();
   }
 
-  getId() {
+  getId(): string {
     return CameraImage.getFullyQualifiedName();
   }
+
+  getResultId(): string {
+    return frc971.vision.ImageMatchResult.getFullyQualifiedName();
+  }
 }