Home | History | Annotate | Download | only in skpdiff
      1 var MAX_SWAP_IMG_SIZE = 400;
      2 var MAGNIFIER_WIDTH = 200;
      3 var MAGNIFIER_HEIGHT = 200;
      4 var MAGNIFIER_HALF_WIDTH = MAGNIFIER_WIDTH * 0.5;
      5 var MAGNIFIER_HALF_HEIGHT = MAGNIFIER_HEIGHT * 0.5;
      6 // TODO add support for a magnified scale factor
      7 var MAGNIFIER_SCALE_FACTOR = 2.0;
      8 
      9 angular.module('diff_viewer', []).
     10 directive('imgCompare', function() {
     11   // Custom directive for comparing (3-way) images
     12   return {
     13       restrict: 'E', // The directive can be used as an element name
     14       replace: true, // The directive replaces itself with the template
     15       template: '<canvas/>',
     16       scope: true,
     17       link: function(scope, elm, attrs, ctrl) {
     18           var image = new Image();
     19           var canvas = elm[0];
     20           var ctx = canvas.getContext('2d');
     21 
     22           var magnifyContent = false;
     23           var maskCanvas = false;
     24 
     25           // When the type attribute changes, load the image and then render
     26           attrs.$observe('type', function(value) {
     27               switch(value) {
     28                 case "alphaMask":
     29                     image.src = scope.record.differencePath;
     30                     maskCanvas = true;
     31                     break;
     32                 case "baseline":
     33                     image.src = scope.record.baselinePath;
     34                     magnifyContent = true;
     35                     break;
     36                 case "test":
     37                     image.src = scope.record.testPath;
     38                     magnifyContent = true;
     39                     break;
     40                 default:
     41                     console.log("Unknown type attribute on <img-compare>: " + value);
     42                     return;
     43               }
     44 
     45               image.onload = function() {
     46                   // compute the scaled image width/height for image and canvas
     47                   var divisor = 1;
     48                   // Make it so the maximum size of an image is MAX_SWAP_IMG_SIZE,
     49                   // and the images are scaled down in halves.
     50                   while ((image.width / divisor) > MAX_SWAP_IMG_SIZE) {
     51                       divisor *= 2;
     52                   }
     53 
     54                   scope.setImgScaleFactor(1 / divisor);
     55 
     56                   // Set canvas to correct size
     57                   canvas.width = image.width * scope.imgScaleFactor;
     58                   canvas.height = image.height * scope.imgScaleFactor;
     59 
     60                   // update the size for non-alphaMask canvas when loading baseline image
     61                   if (!scope.maskSizeUpdated) {
     62                       if (!maskCanvas) {
     63                           scope.updateMaskCanvasSize({width: canvas.width, height: canvas.height});
     64                       }
     65                       scope.maskCanvasSizeUpdated(true);
     66                   }
     67 
     68                   // render the image onto the canvas
     69                   scope.renderImage();
     70               }
     71           });
     72 
     73           // when updatedMaskSize changes, update mask canvas size.
     74           scope.$watch('updatedMaskSize', function(updatedSize) {
     75               if (!maskCanvas) {
     76                   return;
     77               }
     78 
     79               canvas.width = updatedSize.width;
     80               canvas.height = updatedSize.height;
     81           });
     82 
     83           // When the magnify attribute changes, render the magnified rect at
     84           // the default zoom level.
     85           scope.$watch('magnifyCenter', function(magCenter) {
     86               if (!magnifyContent) {
     87                   return;
     88               }
     89 
     90               scope.renderImage();
     91 
     92               if (!magCenter) {
     93                   return;
     94               }
     95 
     96               var magX = magCenter.x - MAGNIFIER_HALF_WIDTH;
     97               var magY = magCenter.y - MAGNIFIER_HALF_HEIGHT;
     98 
     99               var magMaxX = canvas.width - MAGNIFIER_WIDTH;
    100               var magMaxY = canvas.height - MAGNIFIER_HEIGHT;
    101 
    102               var magRect = { x: Math.max(0, Math.min(magX, magMaxX)),
    103                               y: Math.max(0, Math.min(magY, magMaxY)),
    104                               width: MAGNIFIER_WIDTH,
    105                               height: MAGNIFIER_HEIGHT
    106                             };
    107 
    108               var imgRect = { x: (magCenter.x / scope.imgScaleFactor) - MAGNIFIER_HALF_WIDTH,
    109                               y: (magCenter.y  / scope.imgScaleFactor) - MAGNIFIER_HALF_HEIGHT,
    110                               width: MAGNIFIER_WIDTH,
    111                               height: MAGNIFIER_HEIGHT
    112                             };
    113 
    114               // draw the magnified image
    115               ctx.clearRect(magRect.x, magRect.y, magRect.width, magRect.height);
    116               ctx.drawImage(image, imgRect.x, imgRect.y, imgRect.width, imgRect.height,
    117                             magRect.x, magRect.y, magRect.width, magRect.height);
    118 
    119               // draw the outline rect
    120               ctx.beginPath();
    121               ctx.rect(magRect.x, magRect.y, magRect.width, magRect.height);
    122               ctx.lineWidth = 2;
    123               ctx.strokeStyle = 'red';
    124               ctx.stroke();
    125 
    126           });
    127 
    128           // render the image to the canvas. This is often done every frame prior
    129           // to any special effects (i.e. magnification).
    130           scope.renderImage = function() {
    131             ctx.clearRect(0, 0, canvas.width, canvas.height);
    132             ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
    133           };
    134 
    135           // compute a rect (x,y,width,height) that represents the bounding box for
    136           // the magnification effect
    137           scope.computeMagnifierOutline = function(event) {
    138             var scaledWidth = MAGNIFIER_WIDTH * scope.imgScaleFactor;
    139             var scaledHeight = MAGNIFIER_HEIGHT * scope.imgScaleFactor;
    140             return {
    141               x: event.offsetX - (scaledWidth * 0.5),
    142               y: event.offsetY  - (scaledHeight * 0.5),
    143               width: scaledWidth,
    144               height: scaledHeight
    145             };
    146           };
    147 
    148           // event handler for mouse events that triggers the magnification
    149           // effect across the 3 images being compared.
    150           scope.MagnifyDraw = function(event, startMagnify) {
    151               if (startMagnify) {
    152                   scope.setMagnifierState(true);
    153               }  else if (!scope.magnifierOn) {
    154                   return;
    155               }
    156 
    157               scope.renderImage();
    158 
    159               // render the magnifier outline rect
    160               var rect = scope.computeMagnifierOutline(event);
    161               ctx.save();
    162               ctx.beginPath();
    163               ctx.rect(rect.x, rect.y, rect.width, rect.height);
    164               ctx.lineWidth = 2;
    165               ctx.strokeStyle = 'red';
    166               ctx.stroke();
    167               ctx.restore();
    168 
    169               // update scope on baseline / test that will cause them to render
    170               scope.setMagnifyCenter({x: event.offsetX, y: event.offsetY});
    171           };
    172 
    173           // event handler that triggers the end of the magnification effect and
    174           // resets all the canvases to their original state.
    175           scope.MagnifyEnd = function(event) {
    176               scope.renderImage();
    177               // update scope on baseline / test that will cause them to render
    178               scope.setMagnifierState(false);
    179               scope.setMagnifyCenter(undefined);
    180         };
    181       }
    182   };
    183 });
    184 
    185 function ImageController($scope, $http, $location, $timeout, $parse) {
    186   $scope.imgScaleFactor = 1.0;
    187   $scope.magnifierOn = false;
    188   $scope.magnifyCenter = undefined;
    189   $scope.updatedMaskSize = undefined;
    190   $scope.maskSizeUpdated = false;
    191 
    192   $scope.setImgScaleFactor = function(scaleFactor) {
    193     $scope.imgScaleFactor = scaleFactor;
    194   }
    195 
    196   $scope.setMagnifierState = function(magnifierOn) {
    197     $scope.magnifierOn = magnifierOn;
    198   }
    199 
    200   $scope.setMagnifyCenter = function(magnifyCenter) {
    201       $scope.magnifyCenter = magnifyCenter;
    202   }
    203 
    204   $scope.updateMaskCanvasSize = function(updatedSize) {
    205       $scope.updatedMaskSize = updatedSize;
    206   }
    207 
    208   $scope.maskCanvasSizeUpdated = function(flag) {
    209       $scope.maskSizeUpdated = flag;
    210   }
    211 }
    212 
    213 function DiffListController($scope, $http, $location, $timeout, $parse) {
    214     // Detect if we are running the web server version of the viewer. If so, we set a flag and
    215     // enable some extra functionality of the website for rebaselining.
    216     $scope.isDynamic = ($location.protocol() == "http" || $location.protocol() == "https");
    217 
    218     // Label each kind of differ for the sort buttons.
    219     $scope.differs = [
    220         {
    221             "title": "Different Pixels"
    222         },
    223         {
    224             "title": "Perceptual Difference"
    225         }
    226     ];
    227 
    228     // Puts the records within AngularJS scope
    229     $scope.records = SkPDiffRecords.records;
    230 
    231     // Keep track of the index of the last record to change so that shift clicking knows what range
    232     // of records to apply the action to.
    233     $scope.lastSelectedIndex = undefined;
    234 
    235     // Indicates which diff metric is used for sorting
    236     $scope.sortIndex = 1;
    237 
    238     // Called by the sort buttons to adjust the metric used for sorting
    239     $scope.setSortIndex = function(idx) {
    240         $scope.sortIndex = idx;
    241 
    242         // Because the index of things has most likely changed, the ranges of shift clicking no
    243         // longer make sense from the user's point of view. We reset it to avoid confusion.
    244         $scope.lastSelectedIndex = undefined;
    245     };
    246 
    247     // A predicate for pulling out the number used for sorting
    248     $scope.sortingDiffer = function(record) {
    249         return record.diffs[$scope.sortIndex].result;
    250     };
    251 
    252     // Flash status indicator on the page, and then remove it so the style can potentially be
    253     // reapplied later.
    254     $scope.flashStatus = function(success) {
    255         var flashStyle = success ? "success-flash" : "failure-flash";
    256         var flashDurationMillis = success ? 500 : 800;
    257 
    258         // Store the style in the record. The row will pick up the style this way instead of through
    259         // index because index can change with sort order.
    260         $scope.statusClass = flashStyle;
    261 
    262         // The animation cannot be repeated unless the class is removed the element.
    263         $timeout(function() {
    264             $scope.statusClass = "";
    265         }, flashDurationMillis);
    266     };
    267 
    268     $scope.selectedRebaseline = function(index, event) {
    269         // Retrieve the records in the same order they are displayed.
    270         var recordsInOrder = $parse("records | orderBy:sortingDiffer")($scope);
    271 
    272         // If the user is shift clicking, apply the last tick/untick to all elements in between this
    273         // record, and the last one they ticked/unticked.
    274         if (event.shiftKey && $scope.lastSelectedIndex !== undefined) {
    275             var currentAction = recordsInOrder[index].isRebaselined;
    276             var smallerIndex = Math.min($scope.lastSelectedIndex, index);
    277             var largerIndex = Math.max($scope.lastSelectedIndex, index);
    278             for (var recordIndex = smallerIndex; recordIndex <= largerIndex; recordIndex++) {
    279                 recordsInOrder[recordIndex].isRebaselined = currentAction;
    280             }
    281             $scope.lastSelectedIndex = index;
    282         }
    283         else
    284         {
    285             $scope.lastSelectedIndex = index;
    286         }
    287 
    288     };
    289 
    290     $scope.commitRebaselines = function() {
    291         // Gather up all records that have the rebaseline set.
    292         var rebaselines = [];
    293         for (var recordIndex = 0; recordIndex < $scope.records.length; recordIndex++) {
    294             if ($scope.records[recordIndex].isRebaselined) {
    295                 rebaselines.push($scope.records[recordIndex].testPath);
    296             }
    297         }
    298         $http.post("/commit_rebaselines", {
    299             "rebaselines": rebaselines
    300         }).success(function(data) {
    301             $scope.flashStatus(data.success);
    302         }).error(function() {
    303             $scope.flashStatus(false);
    304         });
    305     };
    306 }
    307