]> source.dussan.org Git - gitblit.git/commitdiff
Add a blink comparator and pixel difference to image diffs 231/head 22/222/1
authorTom <tw201207@gmail.com>
Mon, 17 Nov 2014 23:25:41 +0000 (00:25 +0100)
committerTom <tw201207@gmail.com>
Wed, 19 Nov 2014 14:09:16 +0000 (15:09 +0100)
Pixel difference uses CSS mix-blend-mode, which is supported currently
only on Firefox >= 32 and on Safari >= 7.1. Implementation is behind a
Javascript feature test.

For other browsers, there's a blink comparator.

Code changes:

* ImageDiffHandler now takes the page it's used on as argument. We need
  that to get labels. DOM generated is a
  little bit different (new controls).

* Diff pages adapted to new constructor of ImageDiffHandler.

* CSS and Javascript changes implementing the new controls, making use
  of two new static image resources. Since I felt that the new controls
  deserved tooltips, I also gave the opacity slider a tooltip: changed
  to <a>, and slider handle changed from <div> to <span>. CSS ensures
  everything still displays the same (basically display:inline-block).

* Supplied messages for English, French, and German for the new
  tooltips.

Tested on IE8, Safari 6.1.6 & 7.1, Chrome 38, FF 33.1 & FF 3.6.13

src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties
src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties
src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java
src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java
src/main/java/com/gitblit/wicket/pages/ComparePage.java
src/main/java/com/gitblit/wicket/pages/ImageDiffHandler.java
src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js
src/main/resources/blink32.png [new file with mode: 0644]
src/main/resources/gitblit.css
src/main/resources/sub32.png [new file with mode: 0644]

index c1b5a30e238f2c4adf53fdc36104ef0a665ce54a..648ac2a54b51c210635bd0c465aac5cc22020f51 100644 (file)
@@ -757,3 +757,6 @@ gb.diffDeletedFile = File was deleted
 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
index be36ecd182be51778c0183a9e9ac20bc94a786b4..eca3fd2a7026612816a401f2b1cf2762c09907e6 100644 (file)
@@ -750,3 +750,6 @@ gb.diffDeletedFile = Datei wurde gel\u00f6scht
 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
index 1318b1d9dd2b507199d4c9ab0a3a7d718f2053d1..d479b3d66f44beef81664a3217a37c94e44fb596 100644 (file)
@@ -679,3 +679,6 @@ gb.diffDeletedFile = Fichier a \u00e9t\u00e9 effac\u00e9
 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
index 71516ec8265b8d675fe8d2b5e4da9eda53172fdb..ae737a53285c78a7bf76bdf5c3f42e3e21988511 100644 (file)
@@ -52,7 +52,7 @@ public class BlobDiffPage extends RepositoryPage {
                if (StringUtils.isEmpty(baseObjectId)) {\r
                        // use first parent\r
                        RevCommit parent = commit.getParentCount() == 0 ? null : commit.getParent(0);\r
-                       ImageDiffHandler handler = new ImageDiffHandler(getContextUrl(), repositoryName,\r
+                       ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName,\r
                                        parent.getName(), commit.getName(), imageExtensions);\r
                        diff = DiffUtils.getDiff(r, commit, blobPath, DiffOutputType.HTML, handler).content;\r
                        if (handler.getImgDiffCount() > 0) {\r
@@ -63,7 +63,7 @@ public class BlobDiffPage extends RepositoryPage {
                } else {\r
                        // base commit specified\r
                        RevCommit baseCommit = JGitUtils.getCommit(r, baseObjectId);\r
-                       ImageDiffHandler handler = new ImageDiffHandler(getContextUrl(), repositoryName,\r
+                       ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName,\r
                                        baseCommit.getName(), commit.getName(), imageExtensions);\r
                        diff = DiffUtils.getDiff(r, baseCommit, commit, blobPath, DiffOutputType.HTML, handler).content;\r
                        if (handler.getImgDiffCount() > 0) {\r
index e40af5159c24db8b8c291bcf3407bbd3fc32783c..c838dab5500a41bd7e5f6bac19f7fbb6c78a3fbb 100644 (file)
@@ -82,7 +82,7 @@ public class CommitDiffPage extends RepositoryPage {
                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) {
index c0141eba7637070eba3cf2635c90426728913c20..62ae7c25c92d6220f2a403bb4be20df28b0883b4 100644 (file)
@@ -113,7 +113,7 @@ public class ComparePage extends RepositoryPage {
                        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);
index 52bf13b9d231aadba0c4f03d0c22bc1de07cfb76..dc0c5ae8226656a7d1dbf4659641ca0cc65ae1f1 100644 (file)
@@ -18,6 +18,7 @@ package com.gitblit.wicket.pages;
 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 @@ public class ImageDiffHandler implements DiffUtils.BinaryDiffHandler {
        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 @@ public class ImageDiffHandler implements DiffUtils.BinaryDiffHandler {
                                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 @@ public class ImageDiffHandler implements DiffUtils.BinaryDiffHandler {
                                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;
                                        }
@@ -128,6 +141,13 @@ public class ImageDiffHandler implements DiffUtils.BinaryDiffHandler {
                return null;
        }
 
+       /**
+        * 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.
index c98a05a4678ed06d0db36634e2a1bb23398feab3..e993997a88a7be2b887b289e36a2ba4876b7c2ec 100644 (file)
@@ -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 @@ function rangeSlider(elem, options) {
        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 @@ function setup() {
                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 @@ function setup() {
                        }
                });
                $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 @@ function setup() {
                        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 (file)
index 0000000..da59350
Binary files /dev/null and b/src/main/resources/blink32.png differ
index e0570ceb4017fe999e548d38425e3ec48ff56958..a6cc516c3edde46263e9585c794b0a0dc5545759 100644 (file)
@@ -1490,10 +1490,12 @@ img.imgdiff-old {
        user-select: none;\r
        border: 1px solid #F00;\r
 }\r
+\r
 .imgdiff-opa-container {\r
+       display: inline-block;\r
        width: 200px;\r
        height: 4px;\r
-       margin: 12px 35px;\r
+       margin: 12px 35px 6px 35px;\r
        padding: 0;\r
        position: relative;\r
        border: 1px solid #888;\r
@@ -1532,6 +1534,7 @@ img.imgdiff-old {
 }\r
 \r
 .imgdiff-opa-handle {\r
+       display: inline-block;\r
        width: 10px;\r
        height: 10px;\r
        position: absolute;\r
@@ -1549,6 +1552,7 @@ img.imgdiff-old {
 }\r
 \r
 .imgdiff-ovr-handle {\r
+       display: inline-block;\r
        width : 1px;\r
        height: 100%;\r
        top: 0px;\r
@@ -1578,6 +1582,19 @@ img.imgdiff-old {
        /* With CSS: background-image: radial-gradient(5px at 50% 50%, #444, #888, transparent 5px); */\r
 }\r
 \r
+.imgdiff-link {\r
+       margin: 0px 4px;\r
+       text-decoration: none;\r
+       border: none;\r
+}\r
+\r
+.imgdiff-link > img {\r
+       border: 1px solid transparent; /* Avoid jumping when we change the border */\r
+       width: 20px;\r
+       height: 20px;\r
+       margin-bottom: 10px;\r
+}\r
+\r
 /* End image diffs */\r
 \r
 td.changeType {\r
diff --git a/src/main/resources/sub32.png b/src/main/resources/sub32.png
new file mode 100644 (file)
index 0000000..ebcfe13
Binary files /dev/null and b/src/main/resources/sub32.png differ