From 57f05709b0b7072b4d9fb1a355d87dff7c7e3665 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Wed, 19 Nov 2014 11:26:12 -0500
Subject: [PATCH] Merged #222 "Add a blink comparator and pixel difference to image diffs"

---
 src/main/java/com/gitblit/wicket/pages/ImageDiffHandler.java |   32 ++++++++--
 src/main/resources/blink32.png                               |    0 
 src/main/resources/sub32.png                                 |    0 
 src/main/resources/gitblit.css                               |   19 ++++++
 src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java     |    4 
 src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js    |   63 +++++++++++++++++++-
 src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties |    3 +
 src/main/java/com/gitblit/wicket/GitBlitWebApp.properties    |    3 +
 src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties |    3 +
 src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java   |    2 
 src/main/java/com/gitblit/wicket/pages/ComparePage.java      |    2 
 11 files changed, 115 insertions(+), 16 deletions(-)

diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
index c1b5a30..648ac2a 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -757,3 +757,6 @@
 gb.diffRenamedFile = File was renamed from {0}
 gb.diffCopiedFile = File was copied from {0}
 gb.diffTruncated = Diff truncated after the above file
+gb.opacityAdjust = Adjust opacity
+gb.blinkComparator = Blink comparator
+gb.imgdiffSubtract = Subtract (black = identical)
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties
index be36ecd..eca3fd2 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties
@@ -750,3 +750,6 @@
 gb.diffRenamedFile = Datei umbenannt von {0}
 gb.diffCopiedFile = Datei kopiert von {0}
 gb.diffTruncated = Diff nach obiger Datei abgeschnitten
+gb.opacityAdjust = Transparenz
+gb.blinkComparator = Blinkkomparator
+gb.imgdiffSubtract = Pixeldifferenz (schwarz = identisch)
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties
index 1318b1d..d479b3d 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties
@@ -679,3 +679,6 @@
 gb.diffRenamedFile = Fichier renomm\u00e9 de {0}
 gb.diffCopiedFile = Fichier copi\u00e9 de {0}
 gb.diffTruncated = Affichage de diff\u00e9rences supprim\u00e9e apr\u00e8s le fichier ci-dessus
+gb.opacityAdjust = ajuster l'opacit\u00e9
+gb.blinkComparator = Comparateur \u00e0 clignotement
+gb.imgdiffSubtract = Diff\u00e9rence (noir = identique)
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java b/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java
index 71516ec..ae737a5 100644
--- a/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java
@@ -52,7 +52,7 @@
 		if (StringUtils.isEmpty(baseObjectId)) {
 			// use first parent
 			RevCommit parent = commit.getParentCount() == 0 ? null : commit.getParent(0);
-			ImageDiffHandler handler = new ImageDiffHandler(getContextUrl(), repositoryName,
+			ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName,
 					parent.getName(), commit.getName(), imageExtensions);
 			diff = DiffUtils.getDiff(r, commit, blobPath, DiffOutputType.HTML, handler).content;
 			if (handler.getImgDiffCount() > 0) {
@@ -63,7 +63,7 @@
 		} else {
 			// base commit specified
 			RevCommit baseCommit = JGitUtils.getCommit(r, baseObjectId);
-			ImageDiffHandler handler = new ImageDiffHandler(getContextUrl(), repositoryName,
+			ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName,
 					baseCommit.getName(), commit.getName(), imageExtensions);
 			diff = DiffUtils.getDiff(r, baseCommit, commit, blobPath, DiffOutputType.HTML, handler).content;
 			if (handler.getImgDiffCount() > 0) {
diff --git a/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java b/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java
index e40af51..c838dab 100644
--- a/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java
@@ -82,7 +82,7 @@
 		add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
 
 		final List<String> imageExtensions = app().settings().getStrings(Keys.web.imageExtensions);
-		final ImageDiffHandler handler = new ImageDiffHandler(getContextUrl(), repositoryName,
+		final ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName,
 				parents.isEmpty() ? null : parents.get(0), commit.getName(), imageExtensions);
 		final DiffOutput diff = DiffUtils.getCommitDiff(r, commit, DiffOutputType.HTML, handler);
 		if (handler.getImgDiffCount() > 0) {
diff --git a/src/main/java/com/gitblit/wicket/pages/ComparePage.java b/src/main/java/com/gitblit/wicket/pages/ComparePage.java
index c0141eb..62ae7c2 100644
--- a/src/main/java/com/gitblit/wicket/pages/ComparePage.java
+++ b/src/main/java/com/gitblit/wicket/pages/ComparePage.java
@@ -113,7 +113,7 @@
 			toCommitId.setObject(endId);
 
 			final List<String> imageExtensions = app().settings().getStrings(Keys.web.imageExtensions);
-			final ImageDiffHandler handler = new ImageDiffHandler(getContextUrl(), repositoryName,
+			final ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName,
 					fromCommit.getName(), toCommit.getName(), imageExtensions);
 
 			final DiffOutput diff = DiffUtils.getDiff(r, fromCommit, toCommit, DiffOutputType.HTML, handler);
diff --git a/src/main/java/com/gitblit/wicket/pages/ImageDiffHandler.java b/src/main/java/com/gitblit/wicket/pages/ImageDiffHandler.java
index 52bf13b..dc0c5ae 100644
--- a/src/main/java/com/gitblit/wicket/pages/ImageDiffHandler.java
+++ b/src/main/java/com/gitblit/wicket/pages/ImageDiffHandler.java
@@ -18,6 +18,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.List;
 
+import org.apache.wicket.protocol.http.WebApplication;
 import org.apache.wicket.protocol.http.WicketURLEncoder;
 import org.eclipse.jgit.diff.DiffEntry;
 import org.eclipse.jgit.diff.DiffEntry.Side;
@@ -37,14 +38,14 @@
 	private final String oldCommitId;
 	private final String newCommitId;
 	private final String repositoryName;
-	private final String baseUrl;
+	private final BasePage page;
 	private final List<String> imageExtensions;
 
 	private int imgDiffCount = 0;
 
-	public ImageDiffHandler(final String baseUrl, final String repositoryName, final String oldCommitId,
-			final String newCommitId, final List<String> imageExtensions) {
-		this.baseUrl = baseUrl;
+	public ImageDiffHandler(final BasePage page, final String repositoryName, final String oldCommitId, final String newCommitId,
+			final List<String> imageExtensions) {
+		this.page = page;
 		this.repositoryName = repositoryName;
 		this.oldCommitId = oldCommitId;
 		this.newCommitId = newCommitId;
@@ -81,7 +82,19 @@
 				old.appendElement("img").attr("class", "imgdiff-old").attr("id", id).attr("style", "max-width:640px;").attr("src", oldUrl);
 				container.appendElement("img").attr("class", "imgdiff").attr("style", "max-width:640px;").attr("src", newUrl);
 				wrapper.appendElement("br");
-				wrapper.appendElement("div").attr("class", "imgdiff-opa-container").appendElement("div").attr("class", "imgdiff-opa-slider");
+				Element controls = wrapper.appendElement("div");
+				// Opacity slider
+				controls.appendElement("div").attr("class", "imgdiff-opa-container").appendElement("a").attr("class", "imgdiff-opa-slider")
+						.attr("href", "#").attr("title", page.getString("gb.opacityAdjust"));
+				// Blink comparator: find Pluto!
+				controls.appendElement("a").attr("class", "imgdiff-link imgdiff-blink").attr("href", "#")
+						.attr("title", page.getString("gb.blinkComparator"))
+						.appendElement("img").attr("src", getStaticResourceUrl("blink32.png")).attr("width", "20");
+				// Pixel subtraction, initially not displayed, will be shown by imgdiff.js depending on feature test.
+				// (Uses CSS mix-blend-mode, which isn't supported on all browsers yet).
+				controls.appendElement("a").attr("class", "imgdiff-link imgdiff-subtract").attr("href", "#")
+						.attr("title", page.getString("gb.imgdiffSubtract")).attr("style", "display:none;")
+						.appendElement("img").attr("src", getStaticResourceUrl("sub32.png")).attr("width", "20");
 				return builder.toString();
 			}
 			break;
@@ -118,7 +131,7 @@
 				if (ext.equalsIgnoreCase(extension)) {
 					String commitId = Side.NEW.equals(side) ? newCommitId : oldCommitId;
 					if (commitId != null) {
-						return RawServlet.asLink(baseUrl, urlencode(repositoryName), commitId, urlencode(path));
+						return RawServlet.asLink(page.getContextUrl(), urlencode(repositoryName), commitId, urlencode(path));
 					} else {
 						return null;
 					}
@@ -129,6 +142,13 @@
 	}
 
 	/**
+	 * Returns a URL that will fetch the designated static resource from within GitBlit.
+	 */
+	protected String getStaticResourceUrl(String contextRelativePath) {
+		return WebApplication.get().getRequestCycleProcessor().getRequestCodingStrategy().rewriteStaticRelativeUrl(contextRelativePath);
+	}
+
+	/**
 	 * Encode a URL component of a {@link RawServlet} URL in the special way that the servlet expects it. Note that
 	 * the %-encoding used does not encode '&amp;' or '&lt;'. Slashes are not encoded in the result.
 	 *
diff --git a/src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js b/src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js
index c98a05a..e993997 100644
--- a/src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js
+++ b/src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js
@@ -22,7 +22,7 @@
  *
  * The styling of the slider is to be done in CSS. Currently recognized options:
  * - initial: <float> clipped to [0..1], default 0
- * - handleClass: <string> to assign to the handle div element created.
+ * - handleClass: <string> to assign to the handle span element created.
  * If no handleClass is specified, a very plain default style is assigned.
  */
 function rangeSlider(elem, options) {
@@ -30,7 +30,7 @@
 	options.initial = Math.min(1.0, Math.max(0, options.initial));
 	
 	var $elem = $(elem);
-	var $handle = $('<div></div>').css({ position: 'absolute', left: 0, cursor: 'ew-resize' });
+	var $handle = $('<span></span>').css({ position: 'absolute', left: 0, cursor: 'ew-resize' });
 	var $root = $(document.documentElement);
 	var $doc = $(document);	
 	var lastRatio = options.initial;
@@ -144,6 +144,7 @@
 		var opacityAccess = rangeSlider($opacitySlider, {handleClass: 'imgdiff-opa-handle'});
 		var $img = $('#' + this.id.substr(this.id.indexOf('-')+1)); // Here we change opacity
 		var $div = $img.parent(); // This controls visibility: here we change width.
+		var blinking = false;
 		
 		$overlaySlider.on('slider:pos', function(e, data) {
 			var pos = $(data.handle).offset().left;
@@ -167,11 +168,10 @@
 			}
 		});
 		$opacitySlider.on('slider:pos', function(e, data) {
-			if ($div.width() <= 0) overlayAccess.moveAuto(1.0); // Make old image visible in a nice way
+			if ($div.width() <= 0 && !blinking) overlayAccess.moveAuto(1.0); // Make old image visible in a nice way
 			$img.css('opacity', 1.0 - data.ratio);
 		});
-		$opacitySlider.css('cursor', 'pointer');
-		$opacitySlider.on('mousedown', function(e) {
+		$opacitySlider.on('click', function(e) {
 			var newRatio = (e.pageX - $opacitySlider.offset().left) / $opacitySlider.innerWidth();
 			var oldRatio = opacityAccess.getRatio();
 			if (newRatio !== oldRatio) {
@@ -184,6 +184,59 @@
 			e.preventDefault();
 		});
 			
+		// Blinking before and after images is a good way for the human eye to catch differences.
+		var $blinker = $this.find('.imgdiff-blink');
+		var initialOpacity = null;
+		$blinker.on('click', function(e) {
+			if (blinking) {
+				window.clearTimeout(blinking);
+				$blinker.children('img').first().css('border', '1px solid transparent');
+				opacityAccess.setRatio(initialOpacity);
+				blinking = null;
+			} else {
+				$blinker.children('img').first().css('border', '1px solid #AAA');
+				initialOpacity = opacityAccess.getRatio();
+				var currentOpacity = 1.0;
+				function blink() {
+					opacityAccess.setRatio(currentOpacity);
+					currentOpacity = 1.0 - currentOpacity;
+					// Keep frequeny below 2Hz (i.e., delay above 500ms)
+					blinking = window.setTimeout(blink, 600);
+				}
+				if ($div.width() <= 0) {
+					overlayAccess.moveRatio(1.0, 500, blink);
+				} else {
+					blink();
+				}
+			}
+			e.preventDefault();
+		});
+		
+		// Subtracting before and after images is another good way to detect differences. Result will be
+		// black where identical.		
+		if (typeof $img[0].style.mixBlendMode != 'undefined') {
+			// Feature test: does the browser support the mix-blend-mode CSS property from the Compositing 
+			// and Blending Level 1 spec (http://dev.w3.org/fxtf/compositing-1/#mix-blend-mode )?
+			// As of 2014-11, only Firefox >= 32 and Safari >= 7.1 support this. Other browsers will have to
+			// make do with the blink comparator only.
+			var $sub = $this.find('.imgdiff-subtract');
+			$sub.css('display', 'inline-block');
+			$sub.on('click', function (e) {
+				var curr = $img.css('mix-blend-mode');
+				if (curr != 'difference') {
+					curr = 'difference';
+					$sub.children('img').first().css('border', '1px solid #AAA');
+					if ($div.width() <= 0) overlayAccess.moveRatio(1.0, 500);
+					opacityAccess.setRatio(0);
+				} else {
+					curr = 'normal';
+					$sub.children('img').first().css('border', '1px solid transparent');
+					
+				}
+				$img.css('mix-blend-mode', curr);
+				e.preventDefault();
+			});
+		}
 	});
 }
 
diff --git a/src/main/resources/blink32.png b/src/main/resources/blink32.png
new file mode 100644
index 0000000..da59350
--- /dev/null
+++ b/src/main/resources/blink32.png
Binary files differ
diff --git a/src/main/resources/gitblit.css b/src/main/resources/gitblit.css
index e0570ce..a6cc516 100644
--- a/src/main/resources/gitblit.css
+++ b/src/main/resources/gitblit.css
@@ -1490,10 +1490,12 @@
 	user-select: none;
 	border: 1px solid #F00;
 }
+
 .imgdiff-opa-container {
+	display: inline-block;
 	width: 200px;
 	height: 4px;
-	margin: 12px 35px;
+	margin: 12px 35px 6px 35px;
 	padding: 0;
 	position: relative;
 	border: 1px solid #888;
@@ -1532,6 +1534,7 @@
 }
 
 .imgdiff-opa-handle {
+	display: inline-block;
 	width: 10px;
 	height: 10px;
 	position: absolute;
@@ -1549,6 +1552,7 @@
 }
 
 .imgdiff-ovr-handle {
+	display: inline-block;
 	width : 1px;
 	height: 100%;
 	top: 0px;
@@ -1578,6 +1582,19 @@
 	/* With CSS: background-image: radial-gradient(5px at 50% 50%, #444, #888, transparent 5px); */
 }
 
+.imgdiff-link {
+	margin: 0px 4px;
+	text-decoration: none;
+	border: none;
+}
+
+.imgdiff-link > img {
+	border: 1px solid transparent; /* Avoid jumping when we change the border */
+	width: 20px;
+	height: 20px;
+	margin-bottom: 10px;
+}
+
 /* End image diffs */
 
 td.changeType {
diff --git a/src/main/resources/sub32.png b/src/main/resources/sub32.png
new file mode 100644
index 0000000..ebcfe13
--- /dev/null
+++ b/src/main/resources/sub32.png
Binary files differ

--
Gitblit v1.9.1