diff options
author | djsollen@google.com <djsollen@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2013-11-07 19:24:06 +0000 |
---|---|---|
committer | djsollen@google.com <djsollen@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2013-11-07 19:24:06 +0000 |
commit | 513a7bffd344a2bba6e014ec08838ea0bbb8aa68 (patch) | |
tree | 65496eff4ae5305d3db15abc8e39b0dbf76d5aa3 /tools/skpdiff | |
parent | 0cc00c273dcad0fb073df32b38e89d15064ebb0e (diff) |
update skpdiff visualization (image magnification with alpha mask)
R=epoger@google.com
Review URL: https://codereview.chromium.org/29103005
git-svn-id: http://skia.googlecode.com/svn/trunk@12174 2bbb7eff-a529-9590-31e7-b0007b416f81
Diffstat (limited to 'tools/skpdiff')
-rw-r--r-- | tools/skpdiff/SkDiffContext.cpp | 66 | ||||
-rw-r--r-- | tools/skpdiff/SkDiffContext.h | 9 | ||||
-rw-r--r-- | tools/skpdiff/SkDifferentPixelsMetric.h | 7 | ||||
-rw-r--r-- | tools/skpdiff/SkDifferentPixelsMetric_cpu.cpp | 24 | ||||
-rw-r--r-- | tools/skpdiff/SkDifferentPixelsMetric_opencl.cpp | 4 | ||||
-rw-r--r-- | tools/skpdiff/SkImageDiffer.h | 16 | ||||
-rw-r--r-- | tools/skpdiff/diff_viewer.js | 246 | ||||
-rw-r--r-- | tools/skpdiff/skpdiff_main.cpp | 12 | ||||
-rw-r--r-- | tools/skpdiff/viewer.html | 136 |
9 files changed, 369 insertions, 151 deletions
diff --git a/tools/skpdiff/SkDiffContext.cpp b/tools/skpdiff/SkDiffContext.cpp index 07d304b64c..ce1fad9d58 100644 --- a/tools/skpdiff/SkDiffContext.cpp +++ b/tools/skpdiff/SkDiffContext.cpp @@ -41,6 +41,12 @@ SkDiffContext::~SkDiffContext() { } } +void SkDiffContext::setDifferenceDir(const SkString& path) { + if (!path.isEmpty() && sk_mkdir(path.c_str())) { + fDifferenceDir = path; + } +} + void SkDiffContext::setDiffers(const SkTDArray<SkImageDiffer*>& differs) { // Delete whatever the last array of differs was if (NULL != fDiffers) { @@ -55,6 +61,23 @@ void SkDiffContext::setDiffers(const SkTDArray<SkImageDiffer*>& differs) { differs.copy(fDiffers); } +static SkString get_common_prefix(const SkString& a, const SkString& b) { + const size_t maxPrefixLength = SkTMin(a.size(), b.size()); + SkASSERT(maxPrefixLength > 0); + for (size_t x = 0; x < maxPrefixLength; ++x) { + if (a[x] != b[x]) { + SkString result; + result.set(a.c_str(), x); + return result; + } + } + if (a.size() > b.size()) { + return b; + } else { + return a; + } +} + void SkDiffContext::addDiff(const char* baselinePath, const char* testPath) { // Load the images at the paths SkBitmap baselineBitmap; @@ -75,9 +98,21 @@ void SkDiffContext::addDiff(const char* baselinePath, const char* testPath) { newRecord->fNext = fRecords; fRecords = newRecord; + // compute the common name + SkString baseName = SkOSPath::SkBasename(baselinePath); + SkString testName = SkOSPath::SkBasename(testPath); + newRecord->fCommonName = get_common_prefix(baseName, testName); + + bool alphaMaskPending = false; + bool alphaMaskCreated = false; + // Perform each diff for (int differIndex = 0; differIndex < fDifferCount; differIndex++) { SkImageDiffer* differ = fDiffers[differIndex]; + // TODO only enable for one differ + if (!alphaMaskCreated && !fDifferenceDir.isEmpty()) { + alphaMaskPending = differ->enablePOIAlphaMask(); + } int diffID = differ->queueDiff(&baselineBitmap, &testBitmap); if (diffID >= 0) { @@ -91,6 +126,25 @@ void SkDiffContext::addDiff(const char* baselinePath, const char* testPath) { SkIPoint* poi = differ->getPointsOfInterest(diffID); diffData.fPointsOfInterest.append(poiCount, poi); + if (alphaMaskPending + && SkImageDiffer::RESULT_CORRECT != diffData.fResult + && newRecord->fDifferencePath.isEmpty()) { + newRecord->fDifferencePath = SkOSPath::SkPathJoin(fDifferenceDir.c_str(), + newRecord->fCommonName.c_str()); + + // compute the image diff and output it + SkBitmap* alphaMask = differ->getPointsOfInterestAlphaMask(diffID); + SkBitmap copy; + alphaMask->copyTo(©, SkBitmap::kARGB_8888_Config); + SkImageEncoder::EncodeFile(newRecord->fDifferencePath.c_str(), copy, + SkImageEncoder::kPNG_Type, 100); + } + + if (alphaMaskPending) { + alphaMaskPending = false; + alphaMaskCreated = true; + } + // Because we are doing everything synchronously for now, we are done with the diff // after reading it. differ->deleteDiff(diffID); @@ -201,18 +255,8 @@ void SkDiffContext::outputRecords(SkWStream& stream, bool useJSONP) { SkString baselineAbsPath = get_absolute_path(currentRecord->fBaselinePath); SkString testAbsPath = get_absolute_path(currentRecord->fTestPath); - // strip off directory structure and find the common part of the filename - SkString baseName = SkOSPath::SkBasename(baselineAbsPath.c_str()); - SkString testName = SkOSPath::SkBasename(testAbsPath.c_str()); - for (size_t x = 0; x < baseName.size(); ++x) { - if (baseName[x] != testName[x]) { - baseName.insertUnichar(x, '\n'); - break; - } - } - stream.writeText(" \"commonName\": \""); - stream.writeText(baseName.c_str()); + stream.writeText(currentRecord->fCommonName.c_str()); stream.writeText("\",\n"); stream.writeText(" \"differencePath\": \""); diff --git a/tools/skpdiff/SkDiffContext.h b/tools/skpdiff/SkDiffContext.h index 20193b3851..9669ae0ad3 100644 --- a/tools/skpdiff/SkDiffContext.h +++ b/tools/skpdiff/SkDiffContext.h @@ -26,6 +26,12 @@ public: void setThreadCount(int threadCount) { fThreadCount = threadCount; } /** + * Creates the directory if it does not exist and uses it to store differences + * between images. + */ + void setDifferenceDir(const SkString& directory); + + /** * Sets the differs to be used in each diff. Already started diffs will not retroactively use * these. * @param differs An array of differs to use. The array is copied, but not the differs @@ -106,6 +112,7 @@ private: }; struct DiffRecord { + SkString fCommonName; SkString fDifferencePath; SkString fBaselinePath; SkString fTestPath; @@ -121,6 +128,8 @@ private: SkImageDiffer** fDiffers; int fDifferCount; int fThreadCount; + + SkString fDifferenceDir; }; #endif diff --git a/tools/skpdiff/SkDifferentPixelsMetric.h b/tools/skpdiff/SkDifferentPixelsMetric.h index 38fa5ac55c..614f920356 100644 --- a/tools/skpdiff/SkDifferentPixelsMetric.h +++ b/tools/skpdiff/SkDifferentPixelsMetric.h @@ -27,13 +27,17 @@ class SkDifferentPixelsMetric : public SkImageDiffer { #endif public: + SkDifferentPixelsMetric() : fPOIAlphaMask(false) {} + virtual const char* getName() SK_OVERRIDE; + virtual bool enablePOIAlphaMask() SK_OVERRIDE; virtual int queueDiff(SkBitmap* baseline, SkBitmap* test) SK_OVERRIDE; virtual void deleteDiff(int id) SK_OVERRIDE; virtual bool isFinished(int id) SK_OVERRIDE; virtual double getResult(int id) SK_OVERRIDE; virtual int getPointsOfInterestCount(int id) SK_OVERRIDE; virtual SkIPoint* getPointsOfInterest(int id) SK_OVERRIDE; + virtual SkBitmap* getPointsOfInterestAlphaMask(int id) SK_OVERRIDE; protected: #if SK_SUPPORT_OPENCL @@ -41,8 +45,9 @@ protected: #endif private: - struct QueuedDiff; + bool fPOIAlphaMask; + struct QueuedDiff; SkTDArray<QueuedDiff> fQueuedDiffs; #if SK_SUPPORT_OPENCL diff --git a/tools/skpdiff/SkDifferentPixelsMetric_cpu.cpp b/tools/skpdiff/SkDifferentPixelsMetric_cpu.cpp index 4e5e93969c..a3e4a383f2 100644 --- a/tools/skpdiff/SkDifferentPixelsMetric_cpu.cpp +++ b/tools/skpdiff/SkDifferentPixelsMetric_cpu.cpp @@ -16,12 +16,18 @@ struct SkDifferentPixelsMetric::QueuedDiff { bool finished; double result; SkTDArray<SkIPoint>* poi; + SkBitmap poiAlphaMask; }; const char* SkDifferentPixelsMetric::getName() { return "different_pixels"; } +bool SkDifferentPixelsMetric::enablePOIAlphaMask() { + fPOIAlphaMask = true; + return true; +} + int SkDifferentPixelsMetric::queueDiff(SkBitmap* baseline, SkBitmap* test) { double startTime = get_seconds(); int diffID = fQueuedDiffs.count(); @@ -44,6 +50,14 @@ int SkDifferentPixelsMetric::queueDiff(SkBitmap* baseline, SkBitmap* test) { int height = baseline->height(); int differentPixelsCount = 0; + // Prepare the POI alpha mask if needed + if (fPOIAlphaMask) { + diff->poiAlphaMask.setConfig(SkBitmap::kA8_Config, width, height); + diff->poiAlphaMask.allocPixels(); + diff->poiAlphaMask.lockPixels(); + diff->poiAlphaMask.eraseARGB(SK_AlphaOPAQUE, 0, 0, 0); + } + // Prepare the pixels for comparison baseline->lockPixels(); test->lockPixels(); @@ -56,6 +70,9 @@ int SkDifferentPixelsMetric::queueDiff(SkBitmap* baseline, SkBitmap* test) { if (std::memcmp(&baselineRow[x * 4], &testRow[x * 4], 4) != 0) { poi->push()->set(x, y); differentPixelsCount++; + if (fPOIAlphaMask) { + *diff->poiAlphaMask.getAddr8(x,y) = SK_AlphaTRANSPARENT; + } } } } @@ -93,3 +110,10 @@ int SkDifferentPixelsMetric::getPointsOfInterestCount(int id) { SkIPoint* SkDifferentPixelsMetric::getPointsOfInterest(int id) { return fQueuedDiffs[id].poi->begin(); } + +SkBitmap* SkDifferentPixelsMetric::getPointsOfInterestAlphaMask(int id) { + if (fQueuedDiffs[id].poiAlphaMask.empty()) { + return NULL; + } + return &fQueuedDiffs[id].poiAlphaMask; +} diff --git a/tools/skpdiff/SkDifferentPixelsMetric_opencl.cpp b/tools/skpdiff/SkDifferentPixelsMetric_opencl.cpp index f2a02c1c18..b3f5d2d7e0 100644 --- a/tools/skpdiff/SkDifferentPixelsMetric_opencl.cpp +++ b/tools/skpdiff/SkDifferentPixelsMetric_opencl.cpp @@ -47,6 +47,10 @@ const char* SkDifferentPixelsMetric::getName() { return "different_pixels"; } +bool SkDifferentPixelsMetric::enablePOIAlphaMask() { + return false; +} + int SkDifferentPixelsMetric::queueDiff(SkBitmap* baseline, SkBitmap* test) { int diffID = fQueuedDiffs.count(); double startTime = get_seconds(); diff --git a/tools/skpdiff/SkImageDiffer.h b/tools/skpdiff/SkImageDiffer.h index 6c570cbb8e..3b50d5775d 100644 --- a/tools/skpdiff/SkImageDiffer.h +++ b/tools/skpdiff/SkImageDiffer.h @@ -19,6 +19,9 @@ public: SkImageDiffer(); virtual ~SkImageDiffer(); + static const double RESULT_CORRECT = 1.0f; + static const double RESULT_INCORRECT = 0.0f; + /** * Gets a unique and descriptive name of this differ * @return A statically allocated null terminated string that is the name of this differ @@ -37,6 +40,12 @@ public: virtual bool requiresOpenCL() { return false; } /** + * Enables the generation of an alpha mask for all points of interest. + * @return True if the differ supports generating an alpha mask and false otherwise. + */ + virtual bool enablePOIAlphaMask() { return false; } + + /** * Wraps a call to queueDiff by loading the given filenames into SkBitmaps * @param baseline The file path of the baseline image * @param test The file path of the test image @@ -87,6 +96,13 @@ public: */ virtual SkIPoint* getPointsOfInterest(int id) = 0; + /* + * Gets a bitmap containing an alpha mask containing transparent pixels at the points of + * interest for the diff of the given id. The results are only meaningful after the + * queued diff has finished. + * @param id The id of the queued diff to query + */ + virtual SkBitmap* getPointsOfInterestAlphaMask(int id) { return NULL; } protected: diff --git a/tools/skpdiff/diff_viewer.js b/tools/skpdiff/diff_viewer.js index 9c33f84fa1..06a864edf4 100644 --- a/tools/skpdiff/diff_viewer.js +++ b/tools/skpdiff/diff_viewer.js @@ -1,81 +1,185 @@ var MAX_SWAP_IMG_SIZE = 400; +var MAGNIFIER_WIDTH = 200; +var MAGNIFIER_HEIGHT = 200; +var MAGNIFIER_HALF_WIDTH = MAGNIFIER_WIDTH * 0.5; +var MAGNIFIER_HALF_HEIGHT = MAGNIFIER_HEIGHT * 0.5; +// TODO add support for a magnified scale factor +var MAGNIFIER_SCALE_FACTOR = 2.0; angular.module('diff_viewer', []). -config(['$routeProvider', function($routeProvider) { - // Show the list of differences by default - $routeProvider. - otherwise({ templateUrl: '/diff_list.html', controller: DiffListController}); -}]). -directive('swapImg', function() { - // Custom directive for showing an image that gets swapped my mouseover. - return { - restrict: 'E', // The directive can be used as an element name - replace: true, // The directive replaces itself with the template - template: '<canvas ng-mouseenter="swap()" ng-mouseleave="swap()"></canvas>', - scope: { // The attributes below are bound to the scope - leftSrc: '@', - rightSrc: '@', - side: '@' - }, - link: function(scope, elm, attrs, ctrl) { - var leftImage = new Image(); - var rightImage = new Image(); - var ctx = elm[0].getContext('2d'); - - scope.render = function() { - var image; - if (scope.side == "left") { - image = leftImage; - } else { - image = rightImage; - } - - // Make it so the maximum size of an image is MAX_SWAP_IMG_SIZE, and the images are - // scaled down in halves. - var divisor = 1; - while ((image.width / divisor) > MAX_SWAP_IMG_SIZE) { - divisor *= 2; - } - - // Set canvas to correct size and draw the image into it - elm[0].width = image.width / divisor; - elm[0].height = image.height / divisor; - ctx.drawImage(image, 0, 0, elm[0].width, elm[0].height); - }; +directive('imgCompare', function() { + // Custom directive for comparing (3-way) images + return { + restrict: 'E', // The directive can be used as an element name + replace: true, // The directive replaces itself with the template + template: '<canvas/>', + scope: true, + link: function(scope, elm, attrs, ctrl) { + var image = new Image(); + var canvas = elm[0]; + var ctx = canvas.getContext('2d'); + + var magnifyContent = false; + + // When the type attribute changes, load the image and then render + attrs.$observe('type', function(value) { + switch(value) { + case "alphaMask": + image.src = scope.record.differencePath; + break; + case "baseline": + image.src = scope.record.baselinePath; + magnifyContent = true; + break; + case "test": + image.src = scope.record.testPath; + magnifyContent = true; + break; + default: + console.log("Unknown type attribute on <img-compare>: " + value); + return; + } + + image.onload = function() { + // compute the scaled image width/height for image and canvas + var divisor = 1; + // Make it so the maximum size of an image is MAX_SWAP_IMG_SIZE, + // and the images are scaled down in halves. + while ((image.width / divisor) > MAX_SWAP_IMG_SIZE) { + divisor *= 2; + } + + scope.setImgScaleFactor(1 / divisor); + + // Set canvas to correct size + canvas.width = image.width * scope.imgScaleFactor; + canvas.height = image.height * scope.imgScaleFactor; + + // render the image onto the canvas + scope.renderImage(); + } + }); + + // When the magnify attribute changes, render the magnified rect at + // the default zoom level. + scope.$watch('magnifyCenter', function(magCenter) { + if (!magnifyContent) { + return; + } + + scope.renderImage(); + + if (!magCenter) { + return; + } + + var magX = magCenter.x - MAGNIFIER_HALF_WIDTH; + var magY = magCenter.y - MAGNIFIER_HALF_HEIGHT; + + var magMaxX = canvas.width - MAGNIFIER_WIDTH; + var magMaxY = canvas.height - MAGNIFIER_HEIGHT; + + var magRect = { x: Math.max(0, Math.min(magX, magMaxX)), + y: Math.max(0, Math.min(magY, magMaxY)), + width: MAGNIFIER_WIDTH, + height: MAGNIFIER_HEIGHT + }; + + var imgRect = { x: (magCenter.x / scope.imgScaleFactor) - MAGNIFIER_HALF_WIDTH, + y: (magCenter.y / scope.imgScaleFactor) - MAGNIFIER_HALF_HEIGHT, + width: MAGNIFIER_WIDTH, + height: MAGNIFIER_HEIGHT + }; + + // draw the magnified image + ctx.clearRect(magRect.x, magRect.y, magRect.width, magRect.height); + ctx.drawImage(image, imgRect.x, imgRect.y, imgRect.width, imgRect.height, + magRect.x, magRect.y, magRect.width, magRect.height); - // When the leftSrc attribute changes, load the image and then rerender - attrs.$observe('leftSrc', function(value) { - leftImage.src = value; - leftImage.onload = function() { - if (scope.side == "left") { - scope.render(); - } - }; - }); - - // When the rightSrc attribute changes, load the image and then rerender - attrs.$observe('rightSrc', function(value) { - rightImage.src = value; - rightImage.onload = function() { - if (scope.side == "right") { - scope.render(); - } - }; - }); - - // Swap which side to draw onto the canvas and then rerender - scope.swap = function() { - if (scope.side == "left") { - scope.side = "right"; - } else { - scope.side = "left"; - } - scope.render(); + // draw the outline rect + ctx.beginPath(); + ctx.rect(magRect.x, magRect.y, magRect.width, magRect.height); + ctx.lineWidth = 2; + ctx.strokeStyle = 'red'; + ctx.stroke(); + + }); + + // render the image to the canvas. This is often done every frame prior + // to any special effects (i.e. magnification). + scope.renderImage = function() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(image, 0, 0, canvas.width, canvas.height); + }; + + // compute a rect (x,y,width,height) that represents the bounding box for + // the magnification effect + scope.computeMagnifierOutline = function(event) { + var scaledWidth = MAGNIFIER_WIDTH * scope.imgScaleFactor; + var scaledHeight = MAGNIFIER_HEIGHT * scope.imgScaleFactor; + return { + x: event.offsetX - (scaledWidth * 0.5), + y: event.offsetY - (scaledHeight * 0.5), + width: scaledWidth, + height: scaledHeight }; - } - }; + }; + + // event handler for mouse events that triggers the magnification + // effect across the 3 images being compared. + scope.MagnifyDraw = function(event, startMagnify) { + if (startMagnify) { + scope.setMagnifierState(true); + } else if (!scope.magnifierOn) { + return; + } + + scope.renderImage(); + + // render the magnifier outline rect + var rect = scope.computeMagnifierOutline(event); + ctx.save(); + ctx.beginPath(); + ctx.rect(rect.x, rect.y, rect.width, rect.height); + ctx.lineWidth = 2; + ctx.strokeStyle = 'red'; + ctx.stroke(); + ctx.restore(); + + // update scope on baseline / test that will cause them to render + scope.setMagnifyCenter({x: event.offsetX, y: event.offsetY}); + }; + + // event handler that triggers the end of the magnification effect and + // resets all the canvases to their original state. + scope.MagnifyEnd = function(event) { + scope.renderImage(); + // update scope on baseline / test that will cause them to render + scope.setMagnifierState(false); + scope.setMagnifyCenter(undefined); + }; + } + }; }); +function ImageController($scope, $http, $location, $timeout, $parse) { + $scope.imgScaleFactor = 1.0; + $scope.magnifierOn = false; + $scope.magnifyCenter = undefined; + + $scope.setImgScaleFactor = function(scaleFactor) { + $scope.imgScaleFactor = scaleFactor; + } + + $scope.setMagnifierState = function(magnifierOn) { + $scope.magnifierOn = magnifierOn; + } + + $scope.setMagnifyCenter = function(magnifyCenter) { + $scope.magnifyCenter = magnifyCenter; + } +} + function DiffListController($scope, $http, $location, $timeout, $parse) { // Detect if we are running the web server version of the viewer. If so, we set a flag and // enable some extra functionality of the website for rebaselining. diff --git a/tools/skpdiff/skpdiff_main.cpp b/tools/skpdiff/skpdiff_main.cpp index 55640f7f0a..b1bf9173c7 100644 --- a/tools/skpdiff/skpdiff_main.cpp +++ b/tools/skpdiff/skpdiff_main.cpp @@ -38,6 +38,7 @@ DEFINE_string2(differs, d, "", "The names of the differs to use or all of them b DEFINE_string2(folders, f, "", "Compare two folders with identical subfile names: <baseline folder> <test folder>"); DEFINE_string2(patterns, p, "", "Use two patterns to compare images: <baseline> <test>"); DEFINE_string2(output, o, "", "Writes the output of these diffs to output: <output>"); +DEFINE_string(alphaDir, "", "Writes the alpha mask of these diffs to output: <output>"); DEFINE_bool(jsonp, true, "Output JSON with padding"); DEFINE_string(csv, "", "Writes the output of these diffs to a csv file"); DEFINE_int32(threads, -1, "run N threads in parallel [default is derived from CPUs available]"); @@ -186,9 +187,20 @@ int tool_main(int argc, char * argv[]) { } } + if (!FLAGS_alphaDir.isEmpty()) { + if (1 != FLAGS_alphaDir.count()) { + SkDebugf("alphaDir flag expects one argument: <directory>\n"); + return 1; + } + } + SkDiffContext ctx; ctx.setDiffers(chosenDiffers); + if (!FLAGS_alphaDir.isEmpty()) { + ctx.setDifferenceDir(SkString(FLAGS_alphaDir[0])); + } + if (FLAGS_threads >= 0) { ctx.setThreadCount(FLAGS_threads); } diff --git a/tools/skpdiff/viewer.html b/tools/skpdiff/viewer.html index 1d3793bbf5..6ae65f756c 100644 --- a/tools/skpdiff/viewer.html +++ b/tools/skpdiff/viewer.html @@ -9,73 +9,73 @@ <link rel="stylesheet" type="text/css" href="viewer_style.css"> <title>SkPDiff</title> </head> - <body> - <!-- - All templates are being included with the main page to avoid using AJAX, which would force - us to use a webserver. - --> - <script type="text/ng-template" id="/diff_list.html"> - <div ng-class="statusClass"> - <!-- The commit button --> - <div ng-show="isDynamic" class="commit"> - <button ng-click="commitRebaselines()">Commit</button> - </div> - <!-- Give a choice of how to order the differs --> - <div style="margin:8px"> - Show me the worst by metric: - <button ng-repeat="differ in differs" ng-click="setSortIndex($index)"> - <span class="result-{{ $index }}" style="padding-left:12px;"> </span> - {{ differ.title }} - </button> - </div> - <!-- Begin list of differences --> - <table> - <thead> - <tr> - <td ng-show="isDynamic">Rebaseline?</td> - <td>Name</td> - <td>Expected Image</td> - <td>Actual Image</td> - <td>Results</td> - </tr> - </thead> - <tbody> - <!-- - Loops through every record and crates a row for it. This sorts based on the whichever - metric the user chose, and places a limit on the max number of records to show. - --> - <tr ng-repeat="record in records | orderBy:sortingDiffer | limitTo:500" - ng-class="{selected: record.isRebaselined}"> - <td ng-show="isDynamic"> - <input type="checkbox" - ng-model="record.isRebaselined" - ng-click="selectedRebaseline($index, $event)" - ng-class="{lastselected: lastSelectedIndex == $index}" /> - </td> - <td class="common-name">{{ record.commonName }}</td> - <td> - <swap-img left-src="{{ record.baselinePath }}" - right-src="{{ record.testPath }}" - side="left" - class="gm-image left-image" /></td> - <td> - <swap-img left-src="{{ record.baselinePath }}" - right-src="{{ record.testPath }}" - side="right" - class="gm-image right-image" /></td> - <td> - <div ng-repeat="diff in record.diffs" - ng-mouseover="highlight(diff.differName)" - class="result result-{{$index}}"> - <span class="result-button">{{ diff.result }}</span> - </div> - </td> - </tr> - </tbody> - </table> - </div> - </script> - <!-- Whatever template is used is rendered in the following div --> - <div ng-view></div> + <body ng-controller="DiffListController" ng-class="statusClass"> + <!-- The commit button --> + <div ng-show="isDynamic" class="commit"> + <button ng-click="commitRebaselines()">Commit</button> + </div> + <!-- Give a choice of how to order the differs --> + <div style="margin:8px"> + Show me the worst by metric: + <button ng-repeat="differ in differs" ng-click="setSortIndex($index)"> + <span class="result-{{ $index }}" style="padding-left:12px;"> </span> + {{ differ.title }} + </button> + </div> + <!-- Begin list of differences --> + <table> + <thead> + <tr> + <td ng-show="isDynamic">Rebaseline?</td> + <td>Name</td> + <td>Difference Mask</td> + <td>Expected Image</td> + <td>Actual Image</td> + <td>Results</td> + </tr> + </thead> + <tbody> + <!-- + Loops through every record and crates a row for it. This sorts based on the whichever + metric the user chose, and places a limit on the max number of records to show. + --> + <tr ng-repeat="record in records | orderBy:sortingDiffer | limitTo:500" + ng-class="{selected: record.isRebaselined}" + ng-controller="ImageController"> + <td ng-show="isDynamic"> + <input type="checkbox" + ng-model="record.isRebaselined" + ng-click="selectedRebaseline($index, $event)" + ng-class="{lastselected: lastSelectedIndex == $index}" /> + </td> + <td class="common-name">{{ record.commonName }}</td> + <td> + <img-compare type="alphaMask" + class="left-image" + ng-mousedown="MagnifyDraw($event, true)" + ng-mousemove="MagnifyDraw($event, false)" + ng-mouseup="MagnifyEnd($event)" + ng-mouseleave="MagnifyEnd($event)"/> + </td> + <td> + <img-compare type="baseline" + name="{{record.commonName}}" + class="gm-image left-image" /> + </td> + <td> + <img-compare type="test" + name="{{record.commonName}}" + class="gm-image right-image" /> + </td> + <td> + <div ng-repeat="diff in record.diffs" + ng-mouseover="highlight(diff.differName)" + class="result result-{{$index}}"> + <span class="result-button">{{ diff.result }}</span> + </div> + </td> + </tr> + </tbody> + </table> </body> </html> |