16b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner/* 26b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * Copyright (c) 2010 Google Inc. All rights reserved. 36b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * 46b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * Redistribution and use in source and binary forms, with or without 56b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * modification, are permitted provided that the following conditions are 66b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * met: 76b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * 86b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * * Redistributions of source code must retain the above copyright 96b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * notice, this list of conditions and the following disclaimer. 106b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * * Redistributions in binary form must reproduce the above 116b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * copyright notice, this list of conditions and the following disclaimer 126b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * in the documentation and/or other materials provided with the 136b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * distribution. 146b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * * Neither the name of Google Inc. nor the names of its 156b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * contributors may be used to endorse or promote products derived from 166b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * this software without specific prior written permission. 176b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * 186b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 196b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 206b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 216b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 226b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 236b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 246b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 256b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 266b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 276b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 286b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 296b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner */ 306b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 316b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brennervar LOUPE_MAGNIFICATION_FACTOR = 10; 326b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 336b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brennerfunction Loupe() 346b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner{ 356b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner this._node = $('loupe'); 366b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner this._currentCornerX = -1; 376b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner this._currentCornerY = -1; 386b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 396b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var self = this; 406b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 416b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner function handleOutputClick(event) { self._handleOutputClick(event); } 426b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner $('expected-image').addEventListener('click', handleOutputClick); 436b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner $('actual-image').addEventListener('click', handleOutputClick); 446b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner $('diff-canvas').addEventListener('click', handleOutputClick); 456b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 466b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner function handleLoupeClick(event) { self._handleLoupeClick(event); } 476b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner $('expected-loupe').addEventListener('click', handleLoupeClick); 486b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner $('actual-loupe').addEventListener('click', handleLoupeClick); 496b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner $('diff-loupe').addEventListener('click', handleLoupeClick); 506b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 516b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner function hide(event) { self.hide(); } 526b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner $('loupe-close').addEventListener('click', hide); 536b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner} 546b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 556b70adc33054f8aee8c54d0f460458a9df11b8a5Russell BrennerLoupe.prototype._handleOutputClick = function(event) 566b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner{ 576b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner // The -1 compensates for the border around the image/canvas. 586b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner this._showFor(event.offsetX - 1, event.offsetY - 1); 596b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner}; 606b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 616b70adc33054f8aee8c54d0f460458a9df11b8a5Russell BrennerLoupe.prototype._handleLoupeClick = function(event) 626b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner{ 636b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var deltaX = Math.floor(event.offsetX/LOUPE_MAGNIFICATION_FACTOR); 646b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var deltaY = Math.floor(event.offsetY/LOUPE_MAGNIFICATION_FACTOR); 656b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 666b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner this._showFor( 676b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner this._currentCornerX + deltaX, this._currentCornerY + deltaY); 686b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner} 696b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 706b70adc33054f8aee8c54d0f460458a9df11b8a5Russell BrennerLoupe.prototype.hide = function() 716b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner{ 726b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner this._node.style.display = 'none'; 736b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner}; 746b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 756b70adc33054f8aee8c54d0f460458a9df11b8a5Russell BrennerLoupe.prototype._showFor = function(x, y) 766b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner{ 776b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner this._fillFromImage(x, y, 'expected', $('expected-image')); 786b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner this._fillFromImage(x, y, 'actual', $('actual-image')); 796b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner this._fillFromCanvas(x, y, 'diff', $('diff-canvas')); 806b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 816b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner this._node.style.display = ''; 826b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner}; 836b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 846b70adc33054f8aee8c54d0f460458a9df11b8a5Russell BrennerLoupe.prototype._fillFromImage = function(x, y, type, sourceImage) 856b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner{ 866b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var tempCanvas = document.createElement('canvas'); 876b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner tempCanvas.width = sourceImage.width; 886b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner tempCanvas.height = sourceImage.height; 896b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var tempContext = tempCanvas.getContext('2d'); 906b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 916b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner tempContext.drawImage(sourceImage, 0, 0); 926b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 936b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner this._fillFromCanvas(x, y, type, tempCanvas); 946b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner}; 956b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 966b70adc33054f8aee8c54d0f460458a9df11b8a5Russell BrennerLoupe.prototype._fillFromCanvas = function(x, y, type, canvas) 976b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner{ 986b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var context = canvas.getContext('2d'); 996b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var sourceImageData = 1006b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner context.getImageData(0, 0, canvas.width, canvas.height); 1016b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 1026b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var targetCanvas = $(type + '-loupe'); 1036b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var targetContext = targetCanvas.getContext('2d'); 1046b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner targetContext.fillStyle = 'rgba(255, 255, 255, 1)'; 1056b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner targetContext.fillRect(0, 0, targetCanvas.width, targetCanvas.height); 1066b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 1076b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var sourceXOffset = (targetCanvas.width/LOUPE_MAGNIFICATION_FACTOR - 1)/2; 1086b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var sourceYOffset = (targetCanvas.height/LOUPE_MAGNIFICATION_FACTOR - 1)/2; 1096b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 1106b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner function readPixelComponent(x, y, component) { 1116b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var offset = (y * sourceImageData.width + x) * 4 + component; 1126b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner return sourceImageData.data[offset]; 1136b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner } 1146b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 1156b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner for (var i = -sourceXOffset; i <= sourceXOffset; i++) { 1166b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner for (var j = -sourceYOffset; j <= sourceYOffset; j++) { 1176b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var sourceX = x + i; 1186b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var sourceY = y + j; 1196b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 1206b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var sourceR = readPixelComponent(sourceX, sourceY, 0); 1216b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var sourceG = readPixelComponent(sourceX, sourceY, 1); 1226b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var sourceB = readPixelComponent(sourceX, sourceY, 2); 1236b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var sourceA = readPixelComponent(sourceX, sourceY, 3)/255; 1246b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner sourceA = Math.round(sourceA * 10)/10; 1256b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 1266b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var targetX = (i + sourceXOffset) * LOUPE_MAGNIFICATION_FACTOR; 1276b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var targetY = (j + sourceYOffset) * LOUPE_MAGNIFICATION_FACTOR; 1286b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner var colorString = 1296b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner sourceR + ', ' + sourceG + ', ' + sourceB + ', ' + sourceA; 1306b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner targetContext.fillStyle = 'rgba(' + colorString + ')'; 1316b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner targetContext.fillRect( 1326b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner targetX, targetY, 1336b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner LOUPE_MAGNIFICATION_FACTOR, LOUPE_MAGNIFICATION_FACTOR); 1346b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 1356b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner if (i == 0 && j == 0) { 1366b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner $('loupe-coordinate').textContent = sourceX + ', ' + sourceY; 1376b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner $(type + '-loupe-color').textContent = colorString; 1386b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner } 1396b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner } 1406b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner } 1416b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner 1426b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner this._currentCornerX = x - sourceXOffset; 1436b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner this._currentCornerY = y - sourceYOffset; 1446b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner}; 145