blob: 8b56ea5f8ecc8e844ae5dfbf17a039db494b4055 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2011 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
bungeman@google.come3c8ddf2012-12-05 20:13:12 +00007#include "skdiff.h"
8#include "skdiff_html.h"
9#include "skdiff_utils.h"
10#include "SkBitmap.h"
epoger@google.com46256ea2012-05-22 13:45:35 +000011#include "SkData.h"
zachr@google.com6ef5a852013-06-18 21:23:31 +000012#include "SkForceLinking.h"
tomhudson@google.com4b33d282011-04-27 15:39:30 +000013#include "SkImageEncoder.h"
14#include "SkOSFile.h"
15#include "SkStream.h"
bungemana7e9f052016-02-18 08:53:33 -080016#include "../private/SkTDArray.h"
reed76a834a2016-01-03 18:36:05 -080017#include "../private/SkTSearch.h"
tomhudson@google.com4b33d282011-04-27 15:39:30 +000018
bungeman60e0fee2015-08-26 05:15:46 -070019#include <stdlib.h>
20
zachr@google.com6ef5a852013-06-18 21:23:31 +000021__SK_FORCE_IMAGE_DECODER_LINKING;
22
tomhudson@google.com4b33d282011-04-27 15:39:30 +000023/**
24 * skdiff
25 *
26 * Given three directory names, expects to find identically-named files in
27 * each of the first two; the first are treated as a set of baseline,
28 * the second a set of variant images, and a diff image is written into the
29 * third directory for each pair.
tomhudson@google.com7d042802011-07-14 13:15:55 +000030 * Creates an index.html in the current third directory to compare each
tomhudson@google.com4b33d282011-04-27 15:39:30 +000031 * pair that does not match exactly.
epoger@google.com71329d82012-08-16 13:42:13 +000032 * Recursively descends directories, unless run with --norecurse.
epoger@google.combe6188d2012-05-31 15:13:45 +000033 *
34 * Returns zero exit code if all images match across baseDir and comparisonDir.
tomhudson@google.com4b33d282011-04-27 15:39:30 +000035 */
36
epoger@google.coma5f406e2012-05-01 13:26:16 +000037typedef SkTDArray<SkString*> StringArray;
38typedef StringArray FileArray;
epoger@google.com5fd53852012-03-22 18:20:06 +000039
reed3a3baf62015-01-06 07:39:55 -080040static void add_unique_basename(StringArray* array, const SkString& filename) {
41 // trim off dirs
42 const char* src = filename.c_str();
43 const char* trimmed = strrchr(src, SkPATH_SEPARATOR);
44 if (trimmed) {
45 trimmed += 1; // skip the separator
46 } else {
47 trimmed = src;
48 }
49 const char* end = strrchr(trimmed, '.');
50 if (!end) {
51 end = trimmed + strlen(trimmed);
52 }
53 SkString result(trimmed, end - trimmed);
54
55 // only add unique entries
56 for (int i = 0; i < array->count(); ++i) {
57 if (*array->getAt(i) == result) {
58 return;
59 }
60 }
61 *array->append() = new SkString(result);
62}
63
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000064struct DiffSummary {
65 DiffSummary ()
bungeman@google.come3c8ddf2012-12-05 20:13:12 +000066 : fNumMatches(0)
67 , fNumMismatches(0)
68 , fMaxMismatchV(0)
69 , fMaxMismatchPercent(0) { };
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000070
epoger@google.com5fd53852012-03-22 18:20:06 +000071 ~DiffSummary() {
bungeman@google.come3c8ddf2012-12-05 20:13:12 +000072 for (int i = 0; i < DiffRecord::kResultCount; ++i) {
epoger@google.com76222c02012-05-31 15:12:09 +000073 fResultsOfType[i].deleteAll();
74 }
bungeman@google.come3c8ddf2012-12-05 20:13:12 +000075 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
76 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
77 fStatusOfType[base][comparison].deleteAll();
78 }
skia.committer@gmail.com0264fb42012-12-06 02:01:25 +000079 }
epoger@google.com5fd53852012-03-22 18:20:06 +000080 }
81
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000082 uint32_t fNumMatches;
83 uint32_t fNumMismatches;
84 uint32_t fMaxMismatchV;
85 float fMaxMismatchPercent;
86
bungeman@google.come3c8ddf2012-12-05 20:13:12 +000087 FileArray fResultsOfType[DiffRecord::kResultCount];
88 FileArray fStatusOfType[DiffResource::kStatusCount][DiffResource::kStatusCount];
89
reed3a3baf62015-01-06 07:39:55 -080090 StringArray fFailedBaseNames[DiffRecord::kResultCount];
91
bungeman@google.come3c8ddf2012-12-05 20:13:12 +000092 void printContents(const FileArray& fileArray,
93 const char* baseStatus, const char* comparisonStatus,
94 bool listFilenames) {
95 int n = fileArray.count();
96 printf("%d file pairs %s in baseDir and %s in comparisonDir",
97 n, baseStatus, comparisonStatus);
98 if (listFilenames) {
99 printf(": ");
100 for (int i = 0; i < n; ++i) {
101 printf("%s ", fileArray[i]->c_str());
102 }
103 }
104 printf("\n");
105 }
106
107 void printStatus(bool listFilenames,
108 bool failOnStatusType[DiffResource::kStatusCount]
109 [DiffResource::kStatusCount]) {
110 typedef DiffResource::Status Status;
111
112 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
113 Status baseStatus = static_cast<Status>(base);
114 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
115 Status comparisonStatus = static_cast<Status>(comparison);
116 const FileArray& fileArray = fStatusOfType[base][comparison];
117 if (fileArray.count() > 0) {
118 if (failOnStatusType[base][comparison]) {
119 printf(" [*] ");
120 } else {
121 printf(" [_] ");
122 }
123 printContents(fileArray,
124 DiffResource::getStatusDescription(baseStatus),
125 DiffResource::getStatusDescription(comparisonStatus),
126 listFilenames);
127 }
128 }
129 }
130 }
epoger@google.com76222c02012-05-31 15:12:09 +0000131
epoger@google.com3af4ff42012-07-19 17:35:04 +0000132 // Print a line about the contents of this FileArray to stdout.
epoger@google.com46a45962012-07-12 18:16:02 +0000133 void printContents(const FileArray& fileArray, const char* headerText, bool listFilenames) {
epoger@google.com76222c02012-05-31 15:12:09 +0000134 int n = fileArray.count();
epoger@google.com3af4ff42012-07-19 17:35:04 +0000135 printf("%d file pairs %s", n, headerText);
136 if (listFilenames) {
137 printf(": ");
138 for (int i = 0; i < n; ++i) {
139 printf("%s ", fileArray[i]->c_str());
epoger@google.com76222c02012-05-31 15:12:09 +0000140 }
141 }
epoger@google.com3af4ff42012-07-19 17:35:04 +0000142 printf("\n");
epoger@google.com76222c02012-05-31 15:12:09 +0000143 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000144
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000145 void print(bool listFilenames, bool failOnResultType[DiffRecord::kResultCount],
146 bool failOnStatusType[DiffResource::kStatusCount]
147 [DiffResource::kStatusCount]) {
epoger@google.com3af4ff42012-07-19 17:35:04 +0000148 printf("\ncompared %d file pairs:\n", fNumMatches + fNumMismatches);
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000149 for (int resultInt = 0; resultInt < DiffRecord::kResultCount; ++resultInt) {
150 DiffRecord::Result result = static_cast<DiffRecord::Result>(resultInt);
epoger@google.com3af4ff42012-07-19 17:35:04 +0000151 if (failOnResultType[result]) {
152 printf("[*] ");
153 } else {
154 printf("[_] ");
155 }
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000156 printContents(fResultsOfType[result], DiffRecord::getResultDescription(result),
157 listFilenames);
158 if (DiffRecord::kCouldNotCompare_Result == result) {
159 printStatus(listFilenames, failOnStatusType);
160 }
epoger@google.com46a45962012-07-12 18:16:02 +0000161 }
epoger@google.com3af4ff42012-07-19 17:35:04 +0000162 printf("(results marked with [*] will cause nonzero return value)\n");
163 printf("\nnumber of mismatching file pairs: %d\n", fNumMismatches);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000164 if (fNumMismatches > 0) {
165 printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
epoger@google.com46a45962012-07-12 18:16:02 +0000166 printf("Largest area mismatch was %.2f%% of pixels\n",fMaxMismatchPercent);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000167 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000168 }
169
reed3a3baf62015-01-06 07:39:55 -0800170 void printfFailingBaseNames(const char separator[]) {
171 for (int resultInt = 0; resultInt < DiffRecord::kResultCount; ++resultInt) {
172 const StringArray& array = fFailedBaseNames[resultInt];
173 if (array.count()) {
174 printf("%s [%d]%s", DiffRecord::ResultNames[resultInt], array.count(), separator);
175 for (int j = 0; j < array.count(); ++j) {
176 printf("%s%s", array[j]->c_str(), separator);
177 }
178 printf("\n");
179 }
180 }
181 }
182
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000183 void add (DiffRecord* drp) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000184 uint32_t mismatchValue;
epoger@google.com292aff62012-05-16 14:57:28 +0000185
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000186 if (drp->fBase.fFilename.equals(drp->fComparison.fFilename)) {
187 fResultsOfType[drp->fResult].push(new SkString(drp->fBase.fFilename));
188 } else {
189 SkString* blame = new SkString("(");
190 blame->append(drp->fBase.fFilename);
191 blame->append(", ");
192 blame->append(drp->fComparison.fFilename);
193 blame->append(")");
194 fResultsOfType[drp->fResult].push(blame);
195 }
epoger@google.com292aff62012-05-16 14:57:28 +0000196 switch (drp->fResult) {
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000197 case DiffRecord::kEqualBits_Result:
epoger@google.com46256ea2012-05-22 13:45:35 +0000198 fNumMatches++;
199 break;
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000200 case DiffRecord::kEqualPixels_Result:
epoger@google.com292aff62012-05-16 14:57:28 +0000201 fNumMatches++;
202 break;
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000203 case DiffRecord::kDifferentSizes_Result:
epoger@google.com5fd53852012-03-22 18:20:06 +0000204 fNumMismatches++;
epoger@google.com292aff62012-05-16 14:57:28 +0000205 break;
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000206 case DiffRecord::kDifferentPixels_Result:
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000207 fNumMismatches++;
208 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
209 fMaxMismatchPercent = drp->fFractionDifference * 100;
210 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000211 mismatchValue = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
212 drp->fMaxMismatchB);
213 if (mismatchValue > fMaxMismatchV) {
214 fMaxMismatchV = mismatchValue;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000215 }
epoger@google.com292aff62012-05-16 14:57:28 +0000216 break;
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000217 case DiffRecord::kCouldNotCompare_Result:
epoger@google.com46256ea2012-05-22 13:45:35 +0000218 fNumMismatches++;
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000219 fStatusOfType[drp->fBase.fStatus][drp->fComparison.fStatus].push(
220 new SkString(drp->fBase.fFilename));
epoger@google.com46256ea2012-05-22 13:45:35 +0000221 break;
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000222 case DiffRecord::kUnknown_Result:
epoger@google.com46256ea2012-05-22 13:45:35 +0000223 SkDEBUGFAIL("adding uncategorized DiffRecord");
224 break;
225 default:
226 SkDEBUGFAIL("adding DiffRecord with unhandled fResult value");
227 break;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000228 }
reed3a3baf62015-01-06 07:39:55 -0800229
230 switch (drp->fResult) {
231 case DiffRecord::kEqualBits_Result:
232 case DiffRecord::kEqualPixels_Result:
233 break;
234 default:
235 add_unique_basename(&fFailedBaseNames[drp->fResult], drp->fBase.fFilename);
236 break;
237 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000238 }
239};
240
epoger@google.coma5f406e2012-05-01 13:26:16 +0000241/// Returns true if string contains any of these substrings.
242static bool string_contains_any_of(const SkString& string,
243 const StringArray& substrings) {
244 for (int i = 0; i < substrings.count(); i++) {
245 if (string.contains(substrings[i]->c_str())) {
246 return true;
247 }
248 }
249 return false;
250}
251
epoger@google.com71329d82012-08-16 13:42:13 +0000252/// Internal (potentially recursive) implementation of get_file_list.
253static void get_file_list_subdir(const SkString& rootDir, const SkString& subDir,
254 const StringArray& matchSubstrings,
255 const StringArray& nomatchSubstrings,
256 bool recurseIntoSubdirs, FileArray *files) {
257 bool isSubDirEmpty = subDir.isEmpty();
258 SkString dir(rootDir);
259 if (!isSubDirEmpty) {
260 dir.append(PATH_DIV_STR);
261 dir.append(subDir);
262 }
263
264 // Iterate over files (not directories) within dir.
265 SkOSFile::Iter fileIterator(dir.c_str());
266 SkString fileName;
267 while (fileIterator.next(&fileName, false)) {
268 if (fileName.startsWith(".")) {
269 continue;
270 }
271 SkString pathRelativeToRootDir(subDir);
272 if (!isSubDirEmpty) {
273 pathRelativeToRootDir.append(PATH_DIV_STR);
274 }
275 pathRelativeToRootDir.append(fileName);
276 if (string_contains_any_of(pathRelativeToRootDir, matchSubstrings) &&
277 !string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) {
278 files->push(new SkString(pathRelativeToRootDir));
279 }
280 }
281
282 // Recurse into any non-ignored subdirectories.
283 if (recurseIntoSubdirs) {
284 SkOSFile::Iter dirIterator(dir.c_str());
285 SkString dirName;
286 while (dirIterator.next(&dirName, true)) {
287 if (dirName.startsWith(".")) {
288 continue;
289 }
290 SkString pathRelativeToRootDir(subDir);
291 if (!isSubDirEmpty) {
292 pathRelativeToRootDir.append(PATH_DIV_STR);
293 }
294 pathRelativeToRootDir.append(dirName);
295 if (!string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) {
296 get_file_list_subdir(rootDir, pathRelativeToRootDir,
297 matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
298 files);
299 }
300 }
301 }
302}
303
304/// Iterate over dir and get all files whose filename:
305/// - matches any of the substrings in matchSubstrings, but...
306/// - DOES NOT match any of the substrings in nomatchSubstrings
307/// - DOES NOT start with a dot (.)
308/// Adds the matching files to the list in *files.
epoger@google.coma5f406e2012-05-01 13:26:16 +0000309static void get_file_list(const SkString& dir,
310 const StringArray& matchSubstrings,
311 const StringArray& nomatchSubstrings,
epoger@google.com71329d82012-08-16 13:42:13 +0000312 bool recurseIntoSubdirs, FileArray *files) {
313 get_file_list_subdir(dir, SkString(""),
314 matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
315 files);
epoger@google.com5fd53852012-03-22 18:20:06 +0000316}
317
318static void release_file_list(FileArray *files) {
319 files->deleteAll();
320}
321
322/// Comparison routines for qsort, sort by file names.
323static int compare_file_name_metrics(SkString **lhs, SkString **rhs) {
324 return strcmp((*lhs)->c_str(), (*rhs)->c_str());
325}
326
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000327class AutoReleasePixels {
328public:
329 AutoReleasePixels(DiffRecord* drp)
330 : fDrp(drp) {
halcanary96fcdcc2015-08-27 07:41:13 -0700331 SkASSERT(drp != nullptr);
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000332 }
333 ~AutoReleasePixels() {
halcanary96fcdcc2015-08-27 07:41:13 -0700334 fDrp->fBase.fBitmap.setPixelRef(nullptr);
335 fDrp->fComparison.fBitmap.setPixelRef(nullptr);
336 fDrp->fDifference.fBitmap.setPixelRef(nullptr);
337 fDrp->fWhite.fBitmap.setPixelRef(nullptr);
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000338 }
339
340private:
341 DiffRecord* fDrp;
342};
343
344static void get_bounds(DiffResource& resource, const char* name) {
345 if (resource.fBitmap.empty() && !DiffResource::isStatusFailed(resource.fStatus)) {
346 SkAutoDataUnref fileBits(read_file(resource.fFullPath.c_str()));
halcanary96fcdcc2015-08-27 07:41:13 -0700347 if (nullptr == fileBits) {
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000348 SkDebugf("WARNING: couldn't read %s file <%s>\n", name, resource.fFullPath.c_str());
349 resource.fStatus = DiffResource::kCouldNotRead_Status;
350 } else {
msarett667433f2016-03-17 07:17:54 -0700351 get_bitmap(fileBits, resource, true);
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000352 }
353 }
354}
355
356static void get_bounds(DiffRecord& drp) {
357 get_bounds(drp.fBase, "base");
358 get_bounds(drp.fComparison, "comparison");
359}
360
commit-bot@chromium.org93d7bb62014-05-28 18:26:00 +0000361#ifdef SK_OS_WIN
362#define ANSI_COLOR_RED ""
363#define ANSI_COLOR_GREEN ""
364#define ANSI_COLOR_YELLOW ""
365#define ANSI_COLOR_RESET ""
366#else
367#define ANSI_COLOR_RED "\x1b[31m"
368#define ANSI_COLOR_GREEN "\x1b[32m"
369#define ANSI_COLOR_YELLOW "\x1b[33m"
370#define ANSI_COLOR_RESET "\x1b[0m"
371#endif
372
373#define VERBOSE_STATUS(status,color,filename) if (verbose) printf( "[ " color " %10s " ANSI_COLOR_RESET " ] %s\n", status, filename->c_str())
374
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000375/// Creates difference images, returns the number that have a 0 metric.
epoger@google.coma5f406e2012-05-01 13:26:16 +0000376/// If outputDir.isEmpty(), don't write out diff files.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000377static void create_diff_images (DiffMetricProc dmp,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000378 const int colorThreshold,
379 RecordArray* differences,
380 const SkString& baseDir,
381 const SkString& comparisonDir,
382 const SkString& outputDir,
epoger@google.coma5f406e2012-05-01 13:26:16 +0000383 const StringArray& matchSubstrings,
384 const StringArray& nomatchSubstrings,
epoger@google.com71329d82012-08-16 13:42:13 +0000385 bool recurseIntoSubdirs,
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000386 bool getBounds,
commit-bot@chromium.org93d7bb62014-05-28 18:26:00 +0000387 bool verbose,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000388 DiffSummary* summary) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000389 SkASSERT(!baseDir.isEmpty());
390 SkASSERT(!comparisonDir.isEmpty());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000391
epoger@google.com5fd53852012-03-22 18:20:06 +0000392 FileArray baseFiles;
393 FileArray comparisonFiles;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000394
epoger@google.com71329d82012-08-16 13:42:13 +0000395 get_file_list(baseDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, &baseFiles);
396 get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
epoger@google.coma5f406e2012-05-01 13:26:16 +0000397 &comparisonFiles);
epoger@google.com5fd53852012-03-22 18:20:06 +0000398
epoger@google.coma5f406e2012-05-01 13:26:16 +0000399 if (!baseFiles.isEmpty()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +0000400 qsort(baseFiles.begin(), baseFiles.count(), sizeof(SkString*),
401 SkCastForQSort(compare_file_name_metrics));
epoger@google.coma5f406e2012-05-01 13:26:16 +0000402 }
403 if (!comparisonFiles.isEmpty()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +0000404 qsort(comparisonFiles.begin(), comparisonFiles.count(),
405 sizeof(SkString*), SkCastForQSort(compare_file_name_metrics));
epoger@google.coma5f406e2012-05-01 13:26:16 +0000406 }
epoger@google.com66008522012-05-16 17:40:57 +0000407
epoger@google.com5fd53852012-03-22 18:20:06 +0000408 int i = 0;
409 int j = 0;
410
411 while (i < baseFiles.count() &&
412 j < comparisonFiles.count()) {
413
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000414 SkString basePath(baseDir);
415 SkString comparisonPath(comparisonDir);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000416
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000417 DiffRecord *drp = new DiffRecord;
418 int v = strcmp(baseFiles[i]->c_str(), comparisonFiles[j]->c_str());
epoger@google.com5fd53852012-03-22 18:20:06 +0000419
420 if (v < 0) {
421 // in baseDir, but not in comparisonDir
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000422 drp->fResult = DiffRecord::kCouldNotCompare_Result;
423
424 basePath.append(*baseFiles[i]);
425 comparisonPath.append(*baseFiles[i]);
426
427 drp->fBase.fFilename = *baseFiles[i];
428 drp->fBase.fFullPath = basePath;
429 drp->fBase.fStatus = DiffResource::kExists_Status;
430
431 drp->fComparison.fFilename = *baseFiles[i];
432 drp->fComparison.fFullPath = comparisonPath;
433 drp->fComparison.fStatus = DiffResource::kDoesNotExist_Status;
434
commit-bot@chromium.org93d7bb62014-05-28 18:26:00 +0000435 VERBOSE_STATUS("MISSING", ANSI_COLOR_YELLOW, baseFiles[i]);
436
epoger@google.com5fd53852012-03-22 18:20:06 +0000437 ++i;
438 } else if (v > 0) {
439 // in comparisonDir, but not in baseDir
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000440 drp->fResult = DiffRecord::kCouldNotCompare_Result;
441
442 basePath.append(*comparisonFiles[j]);
443 comparisonPath.append(*comparisonFiles[j]);
444
445 drp->fBase.fFilename = *comparisonFiles[j];
446 drp->fBase.fFullPath = basePath;
447 drp->fBase.fStatus = DiffResource::kDoesNotExist_Status;
448
449 drp->fComparison.fFilename = *comparisonFiles[j];
450 drp->fComparison.fFullPath = comparisonPath;
451 drp->fComparison.fStatus = DiffResource::kExists_Status;
452
commit-bot@chromium.org93d7bb62014-05-28 18:26:00 +0000453 VERBOSE_STATUS("MISSING", ANSI_COLOR_YELLOW, comparisonFiles[j]);
454
epoger@google.com5fd53852012-03-22 18:20:06 +0000455 ++j;
456 } else {
epoger@google.com46256ea2012-05-22 13:45:35 +0000457 // Found the same filename in both baseDir and comparisonDir.
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000458 SkASSERT(DiffRecord::kUnknown_Result == drp->fResult);
epoger@google.com5fd53852012-03-22 18:20:06 +0000459
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000460 basePath.append(*baseFiles[i]);
461 comparisonPath.append(*comparisonFiles[j]);
462
463 drp->fBase.fFilename = *baseFiles[i];
464 drp->fBase.fFullPath = basePath;
465 drp->fBase.fStatus = DiffResource::kExists_Status;
466
467 drp->fComparison.fFilename = *comparisonFiles[j];
468 drp->fComparison.fFullPath = comparisonPath;
469 drp->fComparison.fStatus = DiffResource::kExists_Status;
470
471 SkAutoDataUnref baseFileBits(read_file(drp->fBase.fFullPath.c_str()));
bsalomon49f085d2014-09-05 13:34:00 -0700472 if (baseFileBits) {
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000473 drp->fBase.fStatus = DiffResource::kRead_Status;
474 }
475 SkAutoDataUnref comparisonFileBits(read_file(drp->fComparison.fFullPath.c_str()));
bsalomon49f085d2014-09-05 13:34:00 -0700476 if (comparisonFileBits) {
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000477 drp->fComparison.fStatus = DiffResource::kRead_Status;
478 }
halcanary96fcdcc2015-08-27 07:41:13 -0700479 if (nullptr == baseFileBits || nullptr == comparisonFileBits) {
480 if (nullptr == baseFileBits) {
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000481 drp->fBase.fStatus = DiffResource::kCouldNotRead_Status;
commit-bot@chromium.org93d7bb62014-05-28 18:26:00 +0000482 VERBOSE_STATUS("READ FAIL", ANSI_COLOR_RED, baseFiles[i]);
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000483 }
halcanary96fcdcc2015-08-27 07:41:13 -0700484 if (nullptr == comparisonFileBits) {
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000485 drp->fComparison.fStatus = DiffResource::kCouldNotRead_Status;
commit-bot@chromium.org93d7bb62014-05-28 18:26:00 +0000486 VERBOSE_STATUS("READ FAIL", ANSI_COLOR_RED, comparisonFiles[j]);
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000487 }
488 drp->fResult = DiffRecord::kCouldNotCompare_Result;
489
490 } else if (are_buffers_equal(baseFileBits, comparisonFileBits)) {
491 drp->fResult = DiffRecord::kEqualBits_Result;
commit-bot@chromium.org93d7bb62014-05-28 18:26:00 +0000492 VERBOSE_STATUS("MATCH", ANSI_COLOR_GREEN, baseFiles[i]);
epoger@google.com46256ea2012-05-22 13:45:35 +0000493 } else {
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000494 AutoReleasePixels arp(drp);
msarett667433f2016-03-17 07:17:54 -0700495 get_bitmap(baseFileBits, drp->fBase, false);
496 get_bitmap(comparisonFileBits, drp->fComparison, false);
commit-bot@chromium.org93d7bb62014-05-28 18:26:00 +0000497 VERBOSE_STATUS("DIFFERENT", ANSI_COLOR_RED, baseFiles[i]);
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000498 if (DiffResource::kDecoded_Status == drp->fBase.fStatus &&
499 DiffResource::kDecoded_Status == drp->fComparison.fStatus) {
500 create_and_write_diff_image(drp, dmp, colorThreshold,
501 outputDir, drp->fBase.fFilename);
epoger@google.com46256ea2012-05-22 13:45:35 +0000502 } else {
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000503 drp->fResult = DiffRecord::kCouldNotCompare_Result;
epoger@google.com46256ea2012-05-22 13:45:35 +0000504 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000505 }
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000506
epoger@google.com5fd53852012-03-22 18:20:06 +0000507 ++i;
508 ++j;
509 }
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000510
511 if (getBounds) {
512 get_bounds(*drp);
513 }
514 SkASSERT(DiffRecord::kUnknown_Result != drp->fResult);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000515 differences->push(drp);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000516 summary->add(drp);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000517 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000518
519 for (; i < baseFiles.count(); ++i) {
520 // files only in baseDir
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000521 DiffRecord *drp = new DiffRecord();
522 drp->fBase.fFilename = *baseFiles[i];
523 drp->fBase.fFullPath = baseDir;
524 drp->fBase.fFullPath.append(drp->fBase.fFilename);
525 drp->fBase.fStatus = DiffResource::kExists_Status;
526
527 drp->fComparison.fFilename = *baseFiles[i];
528 drp->fComparison.fFullPath = comparisonDir;
529 drp->fComparison.fFullPath.append(drp->fComparison.fFilename);
530 drp->fComparison.fStatus = DiffResource::kDoesNotExist_Status;
531
532 drp->fResult = DiffRecord::kCouldNotCompare_Result;
533 if (getBounds) {
534 get_bounds(*drp);
535 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000536 differences->push(drp);
537 summary->add(drp);
538 }
539
540 for (; j < comparisonFiles.count(); ++j) {
541 // files only in comparisonDir
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000542 DiffRecord *drp = new DiffRecord();
543 drp->fBase.fFilename = *comparisonFiles[j];
544 drp->fBase.fFullPath = baseDir;
545 drp->fBase.fFullPath.append(drp->fBase.fFilename);
546 drp->fBase.fStatus = DiffResource::kDoesNotExist_Status;
547
548 drp->fComparison.fFilename = *comparisonFiles[j];
549 drp->fComparison.fFullPath = comparisonDir;
550 drp->fComparison.fFullPath.append(drp->fComparison.fFilename);
551 drp->fComparison.fStatus = DiffResource::kExists_Status;
552
553 drp->fResult = DiffRecord::kCouldNotCompare_Result;
554 if (getBounds) {
555 get_bounds(*drp);
556 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000557 differences->push(drp);
558 summary->add(drp);
559 }
560
561 release_file_list(&baseFiles);
562 release_file_list(&comparisonFiles);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000563}
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000564
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000565static void usage (char * argv0) {
566 SkDebugf("Skia baseline image diff tool\n");
epoger@google.coma5f406e2012-05-01 13:26:16 +0000567 SkDebugf("\n"
568"Usage: \n"
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000569" %s <baseDir> <comparisonDir> [outputDir] \n", argv0);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000570 SkDebugf(
epoger@google.com46a45962012-07-12 18:16:02 +0000571"\nArguments:"
epoger@google.comdfbf24e2012-07-13 21:22:02 +0000572"\n --failonresult <result>: After comparing all file pairs, exit with nonzero"
573"\n return code (number of file pairs yielding this"
574"\n result) if any file pairs yielded this result."
575"\n This flag may be repeated, in which case the"
576"\n return code will be the number of fail pairs"
577"\n yielding ANY of these results."
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000578"\n --failonstatus <baseStatus> <comparisonStatus>: exit with nonzero return"
579"\n code if any file pairs yielded this status."
epoger@google.com46a45962012-07-12 18:16:02 +0000580"\n --help: display this info"
epoger@google.com46a45962012-07-12 18:16:02 +0000581"\n --listfilenames: list all filenames for each result type in stdout"
epoger@google.comdfbf24e2012-07-13 21:22:02 +0000582"\n --match <substring>: compare files whose filenames contain this substring;"
583"\n if unspecified, compare ALL files."
584"\n this flag may be repeated."
epoger@google.com46a45962012-07-12 18:16:02 +0000585"\n --nodiffs: don't write out image diffs or index.html, just generate"
586"\n report on stdout"
epoger@google.comdfbf24e2012-07-13 21:22:02 +0000587"\n --nomatch <substring>: regardless of --match, DO NOT compare files whose"
588"\n filenames contain this substring."
589"\n this flag may be repeated."
epoger@google.com46a45962012-07-12 18:16:02 +0000590"\n --noprintdirs: do not print the directories used."
epoger@google.com71329d82012-08-16 13:42:13 +0000591"\n --norecurse: do not recurse into subdirectories."
epoger@google.com46a45962012-07-12 18:16:02 +0000592"\n --sortbymaxmismatch: sort by worst color channel mismatch;"
593"\n break ties with -sortbymismatch"
594"\n --sortbymismatch: sort by average color channel mismatch"
595"\n --threshold <n>: only report differences > n (per color channel) [default 0]"
596"\n --weighted: sort by # pixels different weighted by color difference"
597"\n"
598"\n baseDir: directory to read baseline images from."
599"\n comparisonDir: directory to read comparison images from"
600"\n outputDir: directory to write difference images and index.html to;"
601"\n defaults to comparisonDir"
602"\n"
603"\nIf no sort is specified, it will sort by fraction of pixels mismatching."
604"\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000605}
606
epoger@google.com70044cc2012-07-12 18:37:55 +0000607const int kNoError = 0;
608const int kGenericError = -1;
epoger@google.com46a45962012-07-12 18:16:02 +0000609
caryclark@google.com5987f582012-10-02 18:33:14 +0000610int tool_main(int argc, char** argv);
611int tool_main(int argc, char** argv) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000612 DiffMetricProc diffProc = compute_diff_pmcolor;
epoger@google.com28060e72012-06-28 16:47:34 +0000613 int (*sortProc)(const void*, const void*) = compare<CompareDiffMetrics>;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000614
615 // Maximum error tolerated in any one color channel in any one pixel before
616 // a difference is reported.
617 int colorThreshold = 0;
618 SkString baseDir;
619 SkString comparisonDir;
620 SkString outputDir;
epoger@google.comdfbf24e2012-07-13 21:22:02 +0000621
epoger@google.coma5f406e2012-05-01 13:26:16 +0000622 StringArray matchSubstrings;
623 StringArray nomatchSubstrings;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000624
epoger@google.coma5f406e2012-05-01 13:26:16 +0000625 bool generateDiffs = true;
epoger@google.com46a45962012-07-12 18:16:02 +0000626 bool listFilenames = false;
epoger@google.com71329d82012-08-16 13:42:13 +0000627 bool printDirNames = true;
628 bool recurseIntoSubdirs = true;
commit-bot@chromium.org93d7bb62014-05-28 18:26:00 +0000629 bool verbose = false;
reed3a3baf62015-01-06 07:39:55 -0800630 bool listFailingBase = false;
tomhudson@google.com7d042802011-07-14 13:15:55 +0000631
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000632 RecordArray differences;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000633 DiffSummary summary;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000634
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000635 bool failOnResultType[DiffRecord::kResultCount];
636 for (int i = 0; i < DiffRecord::kResultCount; i++) {
epoger@google.com3af4ff42012-07-19 17:35:04 +0000637 failOnResultType[i] = false;
638 }
639
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000640 bool failOnStatusType[DiffResource::kStatusCount][DiffResource::kStatusCount];
641 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
642 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
643 failOnStatusType[base][comparison] = false;
644 }
645 }
646
epoger@google.coma5f406e2012-05-01 13:26:16 +0000647 int i;
648 int numUnflaggedArguments = 0;
649 for (i = 1; i < argc; i++) {
epoger@google.comdfbf24e2012-07-13 21:22:02 +0000650 if (!strcmp(argv[i], "--failonresult")) {
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000651 if (argc == ++i) {
652 SkDebugf("failonresult expects one argument.\n");
653 continue;
654 }
655 DiffRecord::Result type = DiffRecord::getResultByName(argv[i]);
656 if (type != DiffRecord::kResultCount) {
657 failOnResultType[type] = true;
658 } else {
659 SkDebugf("ignoring unrecognized result <%s>\n", argv[i]);
660 }
661 continue;
662 }
663 if (!strcmp(argv[i], "--failonstatus")) {
664 if (argc == ++i) {
665 SkDebugf("failonstatus missing base status.\n");
666 continue;
667 }
668 bool baseStatuses[DiffResource::kStatusCount];
669 if (!DiffResource::getMatchingStatuses(argv[i], baseStatuses)) {
670 SkDebugf("unrecognized base status <%s>\n", argv[i]);
671 }
672
673 if (argc == ++i) {
674 SkDebugf("failonstatus missing comparison status.\n");
675 continue;
676 }
677 bool comparisonStatuses[DiffResource::kStatusCount];
678 if (!DiffResource::getMatchingStatuses(argv[i], comparisonStatuses)) {
679 SkDebugf("unrecognized comarison status <%s>\n", argv[i]);
680 }
681
682 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
683 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
684 failOnStatusType[base][comparison] |=
685 baseStatuses[base] && comparisonStatuses[comparison];
686 }
687 }
epoger@google.coma5f406e2012-05-01 13:26:16 +0000688 continue;
689 }
epoger@google.com46a45962012-07-12 18:16:02 +0000690 if (!strcmp(argv[i], "--help")) {
691 usage(argv[0]);
epoger@google.com70044cc2012-07-12 18:37:55 +0000692 return kNoError;
epoger@google.com46a45962012-07-12 18:16:02 +0000693 }
694 if (!strcmp(argv[i], "--listfilenames")) {
695 listFilenames = true;
epoger@google.coma5f406e2012-05-01 13:26:16 +0000696 continue;
697 }
commit-bot@chromium.org93d7bb62014-05-28 18:26:00 +0000698 if (!strcmp(argv[i], "--verbose")) {
699 verbose = true;
700 continue;
701 }
epoger@google.coma5f406e2012-05-01 13:26:16 +0000702 if (!strcmp(argv[i], "--match")) {
703 matchSubstrings.push(new SkString(argv[++i]));
704 continue;
705 }
epoger@google.com46a45962012-07-12 18:16:02 +0000706 if (!strcmp(argv[i], "--nodiffs")) {
707 generateDiffs = false;
708 continue;
709 }
epoger@google.coma5f406e2012-05-01 13:26:16 +0000710 if (!strcmp(argv[i], "--nomatch")) {
711 nomatchSubstrings.push(new SkString(argv[++i]));
712 continue;
713 }
epoger@google.com46a45962012-07-12 18:16:02 +0000714 if (!strcmp(argv[i], "--noprintdirs")) {
epoger@google.com71329d82012-08-16 13:42:13 +0000715 printDirNames = false;
716 continue;
717 }
718 if (!strcmp(argv[i], "--norecurse")) {
719 recurseIntoSubdirs = false;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000720 continue;
721 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000722 if (!strcmp(argv[i], "--sortbymaxmismatch")) {
epoger@google.com28060e72012-06-28 16:47:34 +0000723 sortProc = compare<CompareDiffMaxMismatches>;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000724 continue;
725 }
epoger@google.com46a45962012-07-12 18:16:02 +0000726 if (!strcmp(argv[i], "--sortbymismatch")) {
727 sortProc = compare<CompareDiffMeanMismatches>;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000728 continue;
729 }
epoger@google.com46a45962012-07-12 18:16:02 +0000730 if (!strcmp(argv[i], "--threshold")) {
731 colorThreshold = atoi(argv[++i]);
732 continue;
733 }
734 if (!strcmp(argv[i], "--weighted")) {
735 sortProc = compare<CompareDiffWeighted>;
keyar@chromium.orga6318192012-07-09 21:01:50 +0000736 continue;
737 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000738 if (argv[i][0] != '-') {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000739 switch (numUnflaggedArguments++) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000740 case 0:
741 baseDir.set(argv[i]);
742 continue;
743 case 1:
744 comparisonDir.set(argv[i]);
745 continue;
746 case 2:
747 outputDir.set(argv[i]);
748 continue;
749 default:
epoger@google.coma5f406e2012-05-01 13:26:16 +0000750 SkDebugf("extra unflagged argument <%s>\n", argv[i]);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000751 usage(argv[0]);
epoger@google.com70044cc2012-07-12 18:37:55 +0000752 return kGenericError;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000753 }
754 }
reed3a3baf62015-01-06 07:39:55 -0800755 if (!strcmp(argv[i], "--listFailingBase")) {
756 listFailingBase = true;
757 continue;
758 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000759
760 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
761 usage(argv[0]);
epoger@google.com70044cc2012-07-12 18:37:55 +0000762 return kGenericError;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000763 }
epoger@google.coma5f406e2012-05-01 13:26:16 +0000764
epoger@google.coma5f406e2012-05-01 13:26:16 +0000765 if (numUnflaggedArguments == 2) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000766 outputDir = comparisonDir;
epoger@google.coma5f406e2012-05-01 13:26:16 +0000767 } else if (numUnflaggedArguments != 3) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000768 usage(argv[0]);
epoger@google.com70044cc2012-07-12 18:37:55 +0000769 return kGenericError;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000770 }
771
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000772 if (!baseDir.endsWith(PATH_DIV_STR)) {
773 baseDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000774 }
epoger@google.com71329d82012-08-16 13:42:13 +0000775 if (printDirNames) {
keyar@chromium.orga6318192012-07-09 21:01:50 +0000776 printf("baseDir is [%s]\n", baseDir.c_str());
777 }
epoger@google.coma5f406e2012-05-01 13:26:16 +0000778
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000779 if (!comparisonDir.endsWith(PATH_DIV_STR)) {
780 comparisonDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000781 }
epoger@google.com71329d82012-08-16 13:42:13 +0000782 if (printDirNames) {
keyar@chromium.orga6318192012-07-09 21:01:50 +0000783 printf("comparisonDir is [%s]\n", comparisonDir.c_str());
784 }
epoger@google.coma5f406e2012-05-01 13:26:16 +0000785
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000786 if (!outputDir.endsWith(PATH_DIV_STR)) {
787 outputDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000788 }
epoger@google.coma5f406e2012-05-01 13:26:16 +0000789 if (generateDiffs) {
epoger@google.com71329d82012-08-16 13:42:13 +0000790 if (printDirNames) {
keyar@chromium.orga6318192012-07-09 21:01:50 +0000791 printf("writing diffs to outputDir is [%s]\n", outputDir.c_str());
792 }
epoger@google.coma5f406e2012-05-01 13:26:16 +0000793 } else {
epoger@google.com71329d82012-08-16 13:42:13 +0000794 if (printDirNames) {
keyar@chromium.orga6318192012-07-09 21:01:50 +0000795 printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str());
796 }
epoger@google.coma5f406e2012-05-01 13:26:16 +0000797 outputDir.set("");
798 }
799
epoger@google.comda4af242012-06-25 18:45:50 +0000800 // If no matchSubstrings were specified, match ALL strings
801 // (except for whatever nomatchSubstrings were specified, if any).
epoger@google.coma5f406e2012-05-01 13:26:16 +0000802 if (matchSubstrings.isEmpty()) {
803 matchSubstrings.push(new SkString(""));
804 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000805
epoger@google.coma611c3e2012-05-18 20:10:06 +0000806 create_diff_images(diffProc, colorThreshold, &differences,
807 baseDir, comparisonDir, outputDir,
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000808 matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, generateDiffs,
commit-bot@chromium.org93d7bb62014-05-28 18:26:00 +0000809 verbose, &summary);
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000810 summary.print(listFilenames, failOnResultType, failOnStatusType);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000811
reed3a3baf62015-01-06 07:39:55 -0800812 if (listFailingBase) {
813 summary.printfFailingBaseNames("\n");
814 }
815
tomhudson@google.com7d042802011-07-14 13:15:55 +0000816 if (differences.count()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +0000817 qsort(differences.begin(), differences.count(),
818 sizeof(DiffRecord*), sortProc);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000819 }
epoger@google.com66008522012-05-16 17:40:57 +0000820
epoger@google.coma5f406e2012-05-01 13:26:16 +0000821 if (generateDiffs) {
822 print_diff_page(summary.fNumMatches, colorThreshold, differences,
823 baseDir, comparisonDir, outputDir);
824 }
epoger@google.com76222c02012-05-31 15:12:09 +0000825
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000826 for (i = 0; i < differences.count(); i++) {
827 delete differences[i];
828 }
epoger@google.coma5f406e2012-05-01 13:26:16 +0000829 matchSubstrings.deleteAll();
830 nomatchSubstrings.deleteAll();
epoger@google.combe6188d2012-05-31 15:13:45 +0000831
epoger@google.comdfbf24e2012-07-13 21:22:02 +0000832 int num_failing_results = 0;
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000833 for (int i = 0; i < DiffRecord::kResultCount; i++) {
epoger@google.com3af4ff42012-07-19 17:35:04 +0000834 if (failOnResultType[i]) {
835 num_failing_results += summary.fResultsOfType[i].count();
836 }
epoger@google.com46a45962012-07-12 18:16:02 +0000837 }
bungeman@google.come3c8ddf2012-12-05 20:13:12 +0000838 if (!failOnResultType[DiffRecord::kCouldNotCompare_Result]) {
839 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
840 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
841 if (failOnStatusType[base][comparison]) {
842 num_failing_results += summary.fStatusOfType[base][comparison].count();
843 }
844 }
845 }
846 }
epoger@google.com28659882012-07-16 18:01:06 +0000847
848 // On Linux (and maybe other platforms too), any results outside of the
849 // range [0...255] are wrapped (mod 256). Do the conversion ourselves, to
850 // make sure that we only return 0 when there were no failures.
851 return (num_failing_results > 255) ? 255 : num_failing_results;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000852}
caryclark@google.com5987f582012-10-02 18:33:14 +0000853
854#if !defined SK_BUILD_FOR_IOS
855int main(int argc, char * const argv[]) {
856 return tool_main(argc, (char**) argv);
857}
858#endif