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