From 850a2e4f9d9793e15532c9bca1dc2ea99208e30d Mon Sep 17 00:00:00 2001
From: Kamil Bielawski <k_bielawski@fastmail.fm>
Date: Sun, 30 Jun 2019 11:56:20 +0200
Subject: [PATCH] Add ignoreAreasColoredWith option support

---
 README.md                                     |  13 +++++++
 demoassets/main.js                            |  15 ++++++--
 index.html                                    |  28 +++++++++++++++
 nodejs-tests/assets/PeopleWithIgnoreMask.png  | Bin 0 -> 91929 bytes
 .../assets/ignoredColorTestResult.png         | Bin 0 -> 83036 bytes
 nodejs-tests/resemble.test.js                 |  33 ++++++++++++++++++
 resemble.js                                   |  26 +++++++++-----
 7 files changed, 105 insertions(+), 10 deletions(-)
 create mode 100644 nodejs-tests/assets/PeopleWithIgnoreMask.png
 create mode 100644 nodejs-tests/assets/ignoredColorTestResult.png

diff --git a/README.md b/README.md
index 3a07cf9..5ed5dd3 100644
--- a/README.md
+++ b/README.md
@@ -123,6 +123,19 @@ resemble.outputSettings({ ignoredBox: box });
 resemble.outputSettings({ ignoredBoxes: [box1, box2] });
 ```
 
+Another way to exclude parts of the image from comparison, is using the `ignoreAreasColoredWith` option.
+Any pixels that match the specified color on a reference image will be excluded from comparison:
+
+```javascript
+const color = {
+    r: 255,
+    g: 0,
+    b: 0,
+    a: 255
+};
+resemble.outputSettings({ ignoreAreasColoredWith: color });
+```
+
 By default, the comparison algorithm skips pixels when the image width or height is larger than 1200 pixels. This is there to mitigate performance issues.
 
 You can modify this behaviour by setting the `largeImageThreshold` option to a different value. Set it to **0** to switch it off completely.
diff --git a/demoassets/main.js b/demoassets/main.js
index 0fe8d97..8e0239d 100644
--- a/demoassets/main.js
+++ b/demoassets/main.js
@@ -217,8 +217,7 @@ $(function() {
                 })
                 .repaint();
             $this.removeClass("active");
-        }
-        if ($this.is("#ignoredBox")) {
+        } else if ($this.is("#ignoredBox")) {
             resembleControl
                 .outputSettings({
                     ignoredBox: {
@@ -230,6 +229,18 @@ $(function() {
                 })
                 .repaint();
             $this.removeClass("active");
+        } else if ($this.is("#ignoredColor")) {
+            resembleControl
+                .outputSettings({
+                    ignoreAreasColoredWith: {
+                        r: parseInt($("#ignored-color-r").val()),
+                        g: parseInt($("#ignored-color-g").val()),
+                        b: parseInt($("#ignored-color-b").val()),
+                        a: parseInt($("#ignored-color-a").val())
+                    }
+                })
+                .repaint();
+            $this.removeClass("active");
         }
     });
 
diff --git a/index.html b/index.html
index 8c291ca..9d436d2 100644
--- a/index.html
+++ b/index.html
@@ -191,6 +191,34 @@ <h2>Compare two images?</h2>
 									</div>
 								</div>
 
+								<br/>
+								<br/>
+
+								<div class="btn-group buttons" style="display:none">
+									<div class="row">
+										<div class="span1">
+											<label>Red</label>
+											<input type="number" class="input-mini" id="ignored-color-r" min="0" max="255" value="255" />
+										</div>
+										<div class="span1">
+											<label>Green</label>
+											<input type="number" class="input-mini" id="ignored-color-g" min="0" max="255" value="0" />
+										</div>
+										<div class="span1">
+											<label>Blue</label>
+											<input type="number" class="input-mini" id="ignored-color-b" min="0" max="255" value="0" />
+										</div>
+										<div class="span1">
+											<label>Alpha</label>
+											<input type="number" class="input-mini" id="ignored-color-a" min="0" max="255" value="255" />
+										</div>
+										<div class="span2">
+											<label>&nbsp;</label>
+											<button class="btn" id="ignoredColor">Set ignored color</button>
+										</div>
+									</div>
+								</div>
+
 								<br/>
 								<br/>
 								<div id="diff-results" style="display:none;">
diff --git a/nodejs-tests/assets/PeopleWithIgnoreMask.png b/nodejs-tests/assets/PeopleWithIgnoreMask.png
new file mode 100644
index 0000000000000000000000000000000000000000..b1659461fcaa68d4ab6005d3d0674b795d424df3
GIT binary patch
diff --git a/nodejs-tests/assets/ignoredColorTestResult.png b/nodejs-tests/assets/ignoredColorTestResult.png
new file mode 100644
index 0000000000000000000000000000000000000000..2761c972989ec4ca0d8f44c13a396d37da9044a7
GIT binary patch
diff --git a/nodejs-tests/resemble.test.js b/nodejs-tests/resemble.test.js
index 007f76c..21a3ea5 100644
--- a/nodejs-tests/resemble.test.js
+++ b/nodejs-tests/resemble.test.js
@@ -264,4 +264,37 @@ describe("resemble", () => {
                 });
         });
     });
+
+    test("partial diff with ignored color", () => {
+        const peopleSrc = `data:image/jpeg;base64,${fs.readFileSync(
+            "./nodejs-tests/assets/PeopleWithIgnoreMask.png",
+            "base64"
+        )}`;
+        const people2Src = `data:image/jpeg;base64,${fs.readFileSync(
+            "./demoassets/People2.jpg",
+            "base64"
+        )}`;
+
+        return new Promise(resolve => {
+            resemble.outputSettings({
+                ignoreAreasColoredWith: {
+                  r: 255,
+                  g: 0,
+                  b: 0,
+                  a: 255
+                }
+            });
+
+            resemble(people2Src)
+                .compareTo(peopleSrc)
+                .onComplete(data => {
+                    const buffer = data.getBuffer();
+                    const comparison = fs.readFileSync(
+                        "./nodejs-tests/assets/ignoredColorTestResult.png"
+                    );
+                    expect(buffer.equals(comparison)).toBe(true);
+                    resolve();
+                });
+        });
+    });
 });
diff --git a/resemble.js b/resemble.js
index c1ff53d..5f76165 100644
--- a/resemble.js
+++ b/resemble.js
@@ -111,6 +111,7 @@ var isNode = new Function(
         var errorType;
         var boundingBoxes;
         var ignoredBoxes;
+	var ignoreAreasColoredWith;
         var largeImageThreshold = 1200;
         var useCrossOrigin = true;
         var data = {};
@@ -151,7 +152,7 @@ var isNode = new Function(
             );
         }
 
-        function withinComparedArea(x, y, width, height) {
+        function withinComparedArea(x, y, width, height, pixel2) {
             var isIncluded = true,
                 i,
                 boundingBox,
@@ -180,6 +181,10 @@ var isNode = new Function(
                 }
             }
 
+	    if (ignoreAreasColoredWith) {
+		return colorsDistance(pixel2, ignoreAreasColoredWith) !== 0;
+	    }
+
             if (selected === undefined && ignored === undefined) {
                 return true;
             }
@@ -616,13 +621,6 @@ var isNode = new Function(
                 }
 
                 var offset = (verticalPos * width + horizontalPos) * 4;
-                var isWithinComparedArea = withinComparedArea(
-                    horizontalPos,
-                    verticalPos,
-                    width,
-                    height
-                );
-
                 if (
                     !getPixelInfo(pixel1, data1, offset, 1) ||
                     !getPixelInfo(pixel2, data2, offset, 2)
@@ -630,6 +628,14 @@ var isNode = new Function(
                     return;
                 }
 
+                var isWithinComparedArea = withinComparedArea(
+                    horizontalPos,
+                    verticalPos,
+                    width,
+                    height,
+		    pixel2
+                );
+
                 if (ignoreColors) {
                     addBrightnessInfo(pixel1);
                     addBrightnessInfo(pixel2);
@@ -845,6 +851,10 @@ var isNode = new Function(
             if (options.ignoredBoxes !== undefined) {
                 ignoredBoxes = options.ignoredBoxes;
             }
+
+	    if (options.ignoreAreasColoredWith !== undefined) {
+	        ignoreAreasColoredWith = options.ignoreAreasColoredWith;
+	    }
         }
 
         function compare(one, two) {
