blob: 6be434b4db17be05f0e85866b5cbd6d17e16e02c [file] [log] [blame]
zachr@google.com6f8e2c52013-08-07 15:43:04 +00001var MAX_SWAP_IMG_SIZE = 400;
djsollen@google.com513a7bf2013-11-07 19:24:06 +00002var MAGNIFIER_WIDTH = 200;
3var MAGNIFIER_HEIGHT = 200;
4var MAGNIFIER_HALF_WIDTH = MAGNIFIER_WIDTH * 0.5;
5var MAGNIFIER_HALF_HEIGHT = MAGNIFIER_HEIGHT * 0.5;
6// TODO add support for a magnified scale factor
7var MAGNIFIER_SCALE_FACTOR = 2.0;
zachr@google.com6f8e2c52013-08-07 15:43:04 +00008
zachr@google.com1bc995e2013-07-12 13:39:53 +00009angular.module('diff_viewer', []).
djsollen@google.com513a7bf2013-11-07 19:24:06 +000010directive('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');
zachr@google.com1bc995e2013-07-12 13:39:53 +000021
djsollen@google.com513a7bf2013-11-07 19:24:06 +000022 var magnifyContent = false;
commit-bot@chromium.org2cce3df2013-12-12 14:55:45 +000023 var maskCanvas = false;
zachr@google.com1bc995e2013-07-12 13:39:53 +000024
djsollen@google.com513a7bf2013-11-07 19:24:06 +000025 // 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;
commit-bot@chromium.org2cce3df2013-12-12 14:55:45 +000030 maskCanvas = true;
djsollen@google.com513a7bf2013-11-07 19:24:06 +000031 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 }
zachr@google.com1bc995e2013-07-12 13:39:53 +000044
djsollen@google.com513a7bf2013-11-07 19:24:06 +000045 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
commit-bot@chromium.org2cce3df2013-12-12 14:55:45 +000060 // 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
djsollen@google.com513a7bf2013-11-07 19:24:06 +000068 // render the image onto the canvas
69 scope.renderImage();
70 }
71 });
72
commit-bot@chromium.org2cce3df2013-12-12 14:55:45 +000073 // 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
djsollen@google.com513a7bf2013-11-07 19:24:06 +000083 // 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
zachr@google.com1bc995e2013-07-12 13:39:53 +0000145 };
djsollen@google.com513a7bf2013-11-07 19:24:06 +0000146 };
zachr@google.com1bc995e2013-07-12 13:39:53 +0000147
djsollen@google.com513a7bf2013-11-07 19:24:06 +0000148 // 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 }
zachr@google.com1bc995e2013-07-12 13:39:53 +0000156
djsollen@google.com513a7bf2013-11-07 19:24:06 +0000157 scope.renderImage();
zachr@google.com1bc995e2013-07-12 13:39:53 +0000158
djsollen@google.com513a7bf2013-11-07 19:24:06 +0000159 // 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 };
zachr@google.com1bc995e2013-07-12 13:39:53 +0000183});
184
djsollen@google.com513a7bf2013-11-07 19:24:06 +0000185function ImageController($scope, $http, $location, $timeout, $parse) {
186 $scope.imgScaleFactor = 1.0;
187 $scope.magnifierOn = false;
188 $scope.magnifyCenter = undefined;
commit-bot@chromium.org2cce3df2013-12-12 14:55:45 +0000189 $scope.updatedMaskSize = undefined;
190 $scope.maskSizeUpdated = false;
djsollen@google.com513a7bf2013-11-07 19:24:06 +0000191
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 }
commit-bot@chromium.org2cce3df2013-12-12 14:55:45 +0000203
204 $scope.updateMaskCanvasSize = function(updatedSize) {
205 $scope.updatedMaskSize = updatedSize;
206 }
207
208 $scope.maskCanvasSizeUpdated = function(flag) {
209 $scope.maskSizeUpdated = flag;
210 }
djsollen@google.com513a7bf2013-11-07 19:24:06 +0000211}
212
zachr@google.com74c5ab12013-08-07 18:06:39 +0000213function 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
zachr@google.com1bc995e2013-07-12 13:39:53 +0000218 // Label each kind of differ for the sort buttons.
zachr@google.com9432c0c2013-07-09 21:08:28 +0000219 $scope.differs = [
220 {
221 "title": "Different Pixels"
222 },
223 {
224 "title": "Perceptual Difference"
225 }
226 ];
zachr@google.com1bc995e2013-07-12 13:39:53 +0000227
228 // Puts the records within AngularJS scope
zachr@google.com9432c0c2013-07-09 21:08:28 +0000229 $scope.records = SkPDiffRecords.records;
zachr@google.com1bc995e2013-07-12 13:39:53 +0000230
zachr@google.com74c5ab12013-08-07 18:06:39 +0000231 // 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
zachr@google.com1bc995e2013-07-12 13:39:53 +0000235 // 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;
zachr@google.com74c5ab12013-08-07 18:06:39 +0000241
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;
zachr@google.com1bc995e2013-07-12 13:39:53 +0000245 };
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 };
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000251
zachr@google.com74c5ab12013-08-07 18:06:39 +0000252 // Flash status indicator on the page, and then remove it so the style can potentially be
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000253 // reapplied later.
zachr@google.com74c5ab12013-08-07 18:06:39 +0000254 $scope.flashStatus = function(success) {
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000255 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.
zachr@google.com74c5ab12013-08-07 18:06:39 +0000260 $scope.statusClass = flashStyle;
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000261
262 // The animation cannot be repeated unless the class is removed the element.
263 $timeout(function() {
zachr@google.com74c5ab12013-08-07 18:06:39 +0000264 $scope.statusClass = "";
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000265 }, flashDurationMillis);
zachr@google.com74c5ab12013-08-07 18:06:39 +0000266 };
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000267
zachr@google.com74c5ab12013-08-07 18:06:39 +0000268 $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
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000300 }).success(function(data) {
zachr@google.com74c5ab12013-08-07 18:06:39 +0000301 $scope.flashStatus(data.success);
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000302 }).error(function() {
zachr@google.com74c5ab12013-08-07 18:06:39 +0000303 $scope.flashStatus(false);
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000304 });
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000305 };
zachr@google.com1bc995e2013-07-12 13:39:53 +0000306}