Add ignore colors toggle to resemble mode

Change-Id: Iead2ef0468f5ec719fd58476d63ad83b5dcf8ecb
diff --git a/gr-image-diff-tool/gr-image-diff-tool.html b/gr-image-diff-tool/gr-image-diff-tool.html
index 68e7f07..7f55f9a 100644
--- a/gr-image-diff-tool/gr-image-diff-tool.html
+++ b/gr-image-diff-tool/gr-image-diff-tool.html
@@ -21,7 +21,10 @@
   <template>
     <style>
       :host {
+        border-top: 1px solid var(--border-color, #ddd);
         display: block;
+        margin-top: .5em;
+        padding: 1em var(--default-horizontal-margin);
       }
     </style>
 
diff --git a/gr-resemble-diff-mode/gr-resemble-diff-mode.html b/gr-resemble-diff-mode/gr-resemble-diff-mode.html
index 6ec3b16..a3ea88e 100644
--- a/gr-resemble-diff-mode/gr-resemble-diff-mode.html
+++ b/gr-resemble-diff-mode/gr-resemble-diff-mode.html
@@ -21,13 +21,37 @@
       :host {
         display: block;
       }
+      h2 {
+        display: none;
+      }
       #imageDiff {
         border: 1px solid var(--border-color, #ddd);
-        display: block;
-        margin: 0 auto;
+      }
+      :host([loading]) #imageDiff {
+        display: none;
+      }
+      :host([loading]) h2 {
+        display: inline;
+        padding: 1em 0;
+      }
+      .diffContainer {
+        display: flex;
+        justify-content: space-around;
+        width: 100%;
       }
     </style>
-    <img id="imageDiff">
+    <div class="diffContainer">
+      <h2>Loading...</h2>
+      <img id="imageDiff">
+    </div>
+    <label>
+      <input
+          id="ignoreColorsToggle"
+          type="checkbox"
+          checked$="[[_ignoreColors]]"
+          on-tap="_handleIgnoreColorsToggle">
+      Ignore colors
+    </label>
   </template>
   <script src="gr-resemble-diff-mode.js"></script>
 </dom-module>
\ No newline at end of file
diff --git a/gr-resemble-diff-mode/gr-resemble-diff-mode.js b/gr-resemble-diff-mode/gr-resemble-diff-mode.js
index 50661b8..1bce2ee 100644
--- a/gr-resemble-diff-mode/gr-resemble-diff-mode.js
+++ b/gr-resemble-diff-mode/gr-resemble-diff-mode.js
@@ -20,36 +20,83 @@
     properties: {
       baseImage: Object,
       revisionImage: Object,
+      _ignoreColors: {
+        type: Boolean,
+        value: false,
+      },
+      loading: {
+        type: Boolean,
+        value: false,
+        reflectToAttribute: true,
+      },
     },
 
     observers: [
       '_handleImageDiff(baseImage, revisionImage)',
     ],
 
-    _handleImageDiff(base, revision) {
-      if (base && revision) {
-        const baseEncoded = this.getDataUrl(base);
-        const revisionEncoded = this.getDataUrl(revision);
-        return this.compareImages(baseEncoded, revisionEncoded).then(src => {
-          this.$.imageDiff.setAttribute('src', src);
-        });
-      }
+    attached() {
+      resemble.outputSettings({
+        errorType: 'movement',
+        largeImageThreshold: 450,
+      });
     },
 
-    compareImages(baseEncoded, revisionEncoded) {
-      return new Promise((resolve, reject) =>
-          resemble(baseEncoded).compareTo(revisionEncoded).onComplete(data => {
-            if (data.error) {
-              reject();
-            } else {
-              resolve(data.getImageDataUrl());
-            }
-          })
-      );
+    _handleImageDiff() {
+      this.reload();
     },
 
-    getDataUrl(image) {
+    _setImageDiffSrc(src) {
+      delete this.$.imageDiff.src;
+      this.$.imageDiff.src = src;
+    },
+
+    _getDataUrl(image) {
       return 'data:' + image['type'] + ';base64,' + image['body'];
     },
+
+    _maybeIgnoreColors(diffProcess, ignoreColors) {
+      if (ignoreColors) {
+        diffProcess.ignoreColors();
+      } else {
+        diffProcess.ignoreNothing();
+      }
+      return diffProcess;
+    },
+
+    _createDiffProcess(base, rev, ignoreColors) {
+      const process = resemble(base).compareTo(rev);
+      return this._maybeIgnoreColors(process, ignoreColors);
+    },
+
+    /**
+     * Reloads the diff. Resemble 1.2.1 seems to have an issue with successive
+     * reloads via the repaint() function, so this implementation creates a
+     * fresh diff each time it is called.
+     *
+     * @return {Promise} resolves if and when the reload succeeds.
+     */
+    reload() {
+      this.loading = true;
+      if (this.baseImage && this.revisionImage) {
+        const base = this._getDataUrl(this.baseImage);
+        const rev = this._getDataUrl(this.revisionImage);
+
+        return new Promise((resolve, reject) => {
+          this._createDiffProcess(base, rev, this._ignoreColors)
+              .onComplete(data => {
+                this._setImageDiffSrc(data.getImageDataUrl());
+                this.loading = false;
+                resolve();
+              });
+        });
+      }
+      this.loading = false;
+    },
+
+    _handleIgnoreColorsToggle() {
+      this._ignoreColors = !this._ignoreColors;
+      this.reload();
+    },
   });
-})();
\ No newline at end of file
+})();
diff --git a/gr-resemble-diff-mode/gr-resemble-diff-mode_test.html b/gr-resemble-diff-mode/gr-resemble-diff-mode_test.html
index 65460cc..c9ffb5d 100644
--- a/gr-resemble-diff-mode/gr-resemble-diff-mode_test.html
+++ b/gr-resemble-diff-mode/gr-resemble-diff-mode_test.html
@@ -38,46 +38,60 @@
 <script>
   suite('gr-resemble-diff-mode tests', () => {
     let element;
+    let sandbox;
 
     setup(() => {
+      sandbox = sinon.sandbox.create();
       element = fixture('basicFixture');
     });
 
+    teardown(() => { sandbox.restore(); })
+
     test('test initialization', () => {
       assert.notEqual(element, null);
       assert.equal(element.baseImage, null);
       assert.equal(element.revisionImage, null);
     });
 
-    test('test getDataUrl', () => {
-      element.baseImage = {
-        type: 'image/png',
-        body: 'SGVsbG8=',
-      };
-      const desiredOutput = 'data:image/png;base64,SGVsbG8=';
-      assert.equal(element.getDataUrl(element.baseImage), desiredOutput);
+    test('_setImageDiffSrc', () => {
+      const img = element.$.imageDiff;
+      const src = 'data:image/png;base64,SGVsbG8=';
+      assert.notOk(img.getAttribute('src'));
+      element._setImageDiffSrc(src);
+      assert.equal(img.getAttribute('src'), src);
     });
 
-    test('test resemble mode image diff', () => {
-      const img = element.$.imageDiff;
-      const expected_result = 'data:image/png;base64,SGVsbG8V29ybGQ==';
-      assert.isFalse(img.hasAttribute('src'));
-      element.baseImage = {
-        type: 'image/png',
-        body: 'SGVsbG8=',
+    test('_maybeIgnoreColors', () => {
+      const dummyProcess = {
+        ignoreColors: sandbox.stub(),
+        ignoreNothing: sandbox.stub(),
       };
-      assert.isFalse(img.hasAttribute('src'));
-      element.revisionImage = {
-        type: 'image/png',
-        body: 'V29ybGQ=',
-      };
+      element._maybeIgnoreColors(dummyProcess, false);
+      assert.isFalse(dummyProcess.ignoreColors.called);
+      assert.isTrue(dummyProcess.ignoreNothing.called);
+      element._maybeIgnoreColors(dummyProcess, true);
+      assert.isTrue(dummyProcess.ignoreColors.called);
+    });
+
+    test('_handleIgnoreColorsToggle', () => {
+      sandbox.stub(element, 'reload');
+      element._ignoreColors = false;
+      assert.isFalse(element.$.ignoreColorsToggle.checked);
+      MockInteractions.tap(element.$.ignoreColorsToggle);
       flushAsynchronousOperations();
-      sinon.stub(element, 'compareImages')
-          .returns(Promise.resolve(expected_result));
-      return element._handleImageDiff(element.baseImage, element.revisionImage)
-          .then(() => {
-            assert.equal(img.src, expected_result);
-          });
+
+      assert.isTrue(element._ignoreColors);
+      assert.isTrue(element.reload.called);
+    });
+
+    test('calls reload', () => {
+      sandbox.stub(element, 'reload');
+      element.baseImage = {};
+      assert.equal(element.reload.callCount, 0);
+      element.revisionImage = {};
+      assert.equal(element.reload.callCount, 1);
+      MockInteractions.tap(element.$.ignoreColorsToggle);
+      assert.equal(element.reload.callCount, 2);
     });
   });
 </script>
\ No newline at end of file