1 /*
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 */
7 #include "skdiff.h"
8 #include "skdiff_html.h"
9 #include "skdiff_utils.h"
10 #include "SkBitmap.h"
11 #include "SkData.h"
12 #include "SkForceLinking.h"
13 #include "SkImageDecoder.h"
14 #include "SkImageEncoder.h"
15 #include "SkOSFile.h"
16 #include "SkStream.h"
17 #include "SkTDArray.h"
18 #include "SkTemplates.h"
19 #include "SkTSearch.h"
20 #include "SkTypes.h"
21
22 __SK_FORCE_IMAGE_DECODER_LINKING;
23
24 /**
25 * skdiff
26 *
27 * Given three directory names, expects to find identically-named files in
28 * each of the first two; the first are treated as a set of baseline,
29 * the second a set of variant images, and a diff image is written into the
30 * third directory for each pair.
31 * Creates an index.html in the current third directory to compare each
32 * pair that does not match exactly.
33 * Recursively descends directories, unless run with --norecurse.
34 *
35 * Returns zero exit code if all images match across baseDir and comparisonDir.
36 */
37
38 typedef SkTDArray<SkString*> StringArray;
39 typedef StringArray FileArray;
40
41 struct DiffSummary {
DiffSummaryDiffSummary42 DiffSummary ()
43 : fNumMatches(0)
44 , fNumMismatches(0)
45 , fMaxMismatchV(0)
46 , fMaxMismatchPercent(0) { };
47
~DiffSummaryDiffSummary48 ~DiffSummary() {
49 for (int i = 0; i < DiffRecord::kResultCount; ++i) {
50 fResultsOfType[i].deleteAll();
51 }
52 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
53 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
54 fStatusOfType[base][comparison].deleteAll();
55 }
56 }
57 }
58
59 uint32_t fNumMatches;
60 uint32_t fNumMismatches;
61 uint32_t fMaxMismatchV;
62 float fMaxMismatchPercent;
63
64 FileArray fResultsOfType[DiffRecord::kResultCount];
65 FileArray fStatusOfType[DiffResource::kStatusCount][DiffResource::kStatusCount];
66
printContentsDiffSummary67 void printContents(const FileArray& fileArray,
68 const char* baseStatus, const char* comparisonStatus,
69 bool listFilenames) {
70 int n = fileArray.count();
71 printf("%d file pairs %s in baseDir and %s in comparisonDir",
72 n, baseStatus, comparisonStatus);
73 if (listFilenames) {
74 printf(": ");
75 for (int i = 0; i < n; ++i) {
76 printf("%s ", fileArray[i]->c_str());
77 }
78 }
79 printf("\n");
80 }
81
printStatusDiffSummary82 void printStatus(bool listFilenames,
83 bool failOnStatusType[DiffResource::kStatusCount]
84 [DiffResource::kStatusCount]) {
85 typedef DiffResource::Status Status;
86
87 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
88 Status baseStatus = static_cast<Status>(base);
89 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
90 Status comparisonStatus = static_cast<Status>(comparison);
91 const FileArray& fileArray = fStatusOfType[base][comparison];
92 if (fileArray.count() > 0) {
93 if (failOnStatusType[base][comparison]) {
94 printf(" [*] ");
95 } else {
96 printf(" [_] ");
97 }
98 printContents(fileArray,
99 DiffResource::getStatusDescription(baseStatus),
100 DiffResource::getStatusDescription(comparisonStatus),
101 listFilenames);
102 }
103 }
104 }
105 }
106
107 // Print a line about the contents of this FileArray to stdout.
printContentsDiffSummary108 void printContents(const FileArray& fileArray, const char* headerText, bool listFilenames) {
109 int n = fileArray.count();
110 printf("%d file pairs %s", n, headerText);
111 if (listFilenames) {
112 printf(": ");
113 for (int i = 0; i < n; ++i) {
114 printf("%s ", fileArray[i]->c_str());
115 }
116 }
117 printf("\n");
118 }
119
printDiffSummary120 void print(bool listFilenames, bool failOnResultType[DiffRecord::kResultCount],
121 bool failOnStatusType[DiffResource::kStatusCount]
122 [DiffResource::kStatusCount]) {
123 printf("\ncompared %d file pairs:\n", fNumMatches + fNumMismatches);
124 for (int resultInt = 0; resultInt < DiffRecord::kResultCount; ++resultInt) {
125 DiffRecord::Result result = static_cast<DiffRecord::Result>(resultInt);
126 if (failOnResultType[result]) {
127 printf("[*] ");
128 } else {
129 printf("[_] ");
130 }
131 printContents(fResultsOfType[result], DiffRecord::getResultDescription(result),
132 listFilenames);
133 if (DiffRecord::kCouldNotCompare_Result == result) {
134 printStatus(listFilenames, failOnStatusType);
135 }
136 }
137 printf("(results marked with [*] will cause nonzero return value)\n");
138 printf("\nnumber of mismatching file pairs: %d\n", fNumMismatches);
139 if (fNumMismatches > 0) {
140 printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
141 printf("Largest area mismatch was %.2f%% of pixels\n",fMaxMismatchPercent);
142 }
143 }
144
addDiffSummary145 void add (DiffRecord* drp) {
146 uint32_t mismatchValue;
147
148 if (drp->fBase.fFilename.equals(drp->fComparison.fFilename)) {
149 fResultsOfType[drp->fResult].push(new SkString(drp->fBase.fFilename));
150 } else {
151 SkString* blame = new SkString("(");
152 blame->append(drp->fBase.fFilename);
153 blame->append(", ");
154 blame->append(drp->fComparison.fFilename);
155 blame->append(")");
156 fResultsOfType[drp->fResult].push(blame);
157 }
158 switch (drp->fResult) {
159 case DiffRecord::kEqualBits_Result:
160 fNumMatches++;
161 break;
162 case DiffRecord::kEqualPixels_Result:
163 fNumMatches++;
164 break;
165 case DiffRecord::kDifferentSizes_Result:
166 fNumMismatches++;
167 break;
168 case DiffRecord::kDifferentPixels_Result:
169 fNumMismatches++;
170 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
171 fMaxMismatchPercent = drp->fFractionDifference * 100;
172 }
173 mismatchValue = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
174 drp->fMaxMismatchB);
175 if (mismatchValue > fMaxMismatchV) {
176 fMaxMismatchV = mismatchValue;
177 }
178 break;
179 case DiffRecord::kCouldNotCompare_Result:
180 fNumMismatches++;
181 fStatusOfType[drp->fBase.fStatus][drp->fComparison.fStatus].push(
182 new SkString(drp->fBase.fFilename));
183 break;
184 case DiffRecord::kUnknown_Result:
185 SkDEBUGFAIL("adding uncategorized DiffRecord");
186 break;
187 default:
188 SkDEBUGFAIL("adding DiffRecord with unhandled fResult value");
189 break;
190 }
191 }
192 };
193
194 /// Returns true if string contains any of these substrings.
string_contains_any_of(const SkString & string,const StringArray & substrings)195 static bool string_contains_any_of(const SkString& string,
196 const StringArray& substrings) {
197 for (int i = 0; i < substrings.count(); i++) {
198 if (string.contains(substrings[i]->c_str())) {
199 return true;
200 }
201 }
202 return false;
203 }
204
205 /// Internal (potentially recursive) implementation of get_file_list.
get_file_list_subdir(const SkString & rootDir,const SkString & subDir,const StringArray & matchSubstrings,const StringArray & nomatchSubstrings,bool recurseIntoSubdirs,FileArray * files)206 static void get_file_list_subdir(const SkString& rootDir, const SkString& subDir,
207 const StringArray& matchSubstrings,
208 const StringArray& nomatchSubstrings,
209 bool recurseIntoSubdirs, FileArray *files) {
210 bool isSubDirEmpty = subDir.isEmpty();
211 SkString dir(rootDir);
212 if (!isSubDirEmpty) {
213 dir.append(PATH_DIV_STR);
214 dir.append(subDir);
215 }
216
217 // Iterate over files (not directories) within dir.
218 SkOSFile::Iter fileIterator(dir.c_str());
219 SkString fileName;
220 while (fileIterator.next(&fileName, false)) {
221 if (fileName.startsWith(".")) {
222 continue;
223 }
224 SkString pathRelativeToRootDir(subDir);
225 if (!isSubDirEmpty) {
226 pathRelativeToRootDir.append(PATH_DIV_STR);
227 }
228 pathRelativeToRootDir.append(fileName);
229 if (string_contains_any_of(pathRelativeToRootDir, matchSubstrings) &&
230 !string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) {
231 files->push(new SkString(pathRelativeToRootDir));
232 }
233 }
234
235 // Recurse into any non-ignored subdirectories.
236 if (recurseIntoSubdirs) {
237 SkOSFile::Iter dirIterator(dir.c_str());
238 SkString dirName;
239 while (dirIterator.next(&dirName, true)) {
240 if (dirName.startsWith(".")) {
241 continue;
242 }
243 SkString pathRelativeToRootDir(subDir);
244 if (!isSubDirEmpty) {
245 pathRelativeToRootDir.append(PATH_DIV_STR);
246 }
247 pathRelativeToRootDir.append(dirName);
248 if (!string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) {
249 get_file_list_subdir(rootDir, pathRelativeToRootDir,
250 matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
251 files);
252 }
253 }
254 }
255 }
256
257 /// Iterate over dir and get all files whose filename:
258 /// - matches any of the substrings in matchSubstrings, but...
259 /// - DOES NOT match any of the substrings in nomatchSubstrings
260 /// - DOES NOT start with a dot (.)
261 /// Adds the matching files to the list in *files.
get_file_list(const SkString & dir,const StringArray & matchSubstrings,const StringArray & nomatchSubstrings,bool recurseIntoSubdirs,FileArray * files)262 static void get_file_list(const SkString& dir,
263 const StringArray& matchSubstrings,
264 const StringArray& nomatchSubstrings,
265 bool recurseIntoSubdirs, FileArray *files) {
266 get_file_list_subdir(dir, SkString(""),
267 matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
268 files);
269 }
270
release_file_list(FileArray * files)271 static void release_file_list(FileArray *files) {
272 files->deleteAll();
273 }
274
275 /// Comparison routines for qsort, sort by file names.
compare_file_name_metrics(SkString ** lhs,SkString ** rhs)276 static int compare_file_name_metrics(SkString **lhs, SkString **rhs) {
277 return strcmp((*lhs)->c_str(), (*rhs)->c_str());
278 }
279
280 class AutoReleasePixels {
281 public:
AutoReleasePixels(DiffRecord * drp)282 AutoReleasePixels(DiffRecord* drp)
283 : fDrp(drp) {
284 SkASSERT(drp != NULL);
285 }
~AutoReleasePixels()286 ~AutoReleasePixels() {
287 fDrp->fBase.fBitmap.setPixelRef(NULL);
288 fDrp->fComparison.fBitmap.setPixelRef(NULL);
289 fDrp->fDifference.fBitmap.setPixelRef(NULL);
290 fDrp->fWhite.fBitmap.setPixelRef(NULL);
291 }
292
293 private:
294 DiffRecord* fDrp;
295 };
296
get_bounds(DiffResource & resource,const char * name)297 static void get_bounds(DiffResource& resource, const char* name) {
298 if (resource.fBitmap.empty() && !DiffResource::isStatusFailed(resource.fStatus)) {
299 SkAutoDataUnref fileBits(read_file(resource.fFullPath.c_str()));
300 if (NULL == fileBits) {
301 SkDebugf("WARNING: couldn't read %s file <%s>\n", name, resource.fFullPath.c_str());
302 resource.fStatus = DiffResource::kCouldNotRead_Status;
303 } else {
304 get_bitmap(fileBits, resource, SkImageDecoder::kDecodeBounds_Mode);
305 }
306 }
307 }
308
get_bounds(DiffRecord & drp)309 static void get_bounds(DiffRecord& drp) {
310 get_bounds(drp.fBase, "base");
311 get_bounds(drp.fComparison, "comparison");
312 }
313
314 /// Creates difference images, returns the number that have a 0 metric.
315 /// If outputDir.isEmpty(), don't write out diff files.
create_diff_images(DiffMetricProc dmp,const int colorThreshold,RecordArray * differences,const SkString & baseDir,const SkString & comparisonDir,const SkString & outputDir,const StringArray & matchSubstrings,const StringArray & nomatchSubstrings,bool recurseIntoSubdirs,bool getBounds,DiffSummary * summary)316 static void create_diff_images (DiffMetricProc dmp,
317 const int colorThreshold,
318 RecordArray* differences,
319 const SkString& baseDir,
320 const SkString& comparisonDir,
321 const SkString& outputDir,
322 const StringArray& matchSubstrings,
323 const StringArray& nomatchSubstrings,
324 bool recurseIntoSubdirs,
325 bool getBounds,
326 DiffSummary* summary) {
327 SkASSERT(!baseDir.isEmpty());
328 SkASSERT(!comparisonDir.isEmpty());
329
330 FileArray baseFiles;
331 FileArray comparisonFiles;
332
333 get_file_list(baseDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, &baseFiles);
334 get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
335 &comparisonFiles);
336
337 if (!baseFiles.isEmpty()) {
338 qsort(baseFiles.begin(), baseFiles.count(), sizeof(SkString*),
339 SkCastForQSort(compare_file_name_metrics));
340 }
341 if (!comparisonFiles.isEmpty()) {
342 qsort(comparisonFiles.begin(), comparisonFiles.count(),
343 sizeof(SkString*), SkCastForQSort(compare_file_name_metrics));
344 }
345
346 int i = 0;
347 int j = 0;
348
349 while (i < baseFiles.count() &&
350 j < comparisonFiles.count()) {
351
352 SkString basePath(baseDir);
353 SkString comparisonPath(comparisonDir);
354
355 DiffRecord *drp = new DiffRecord;
356 int v = strcmp(baseFiles[i]->c_str(), comparisonFiles[j]->c_str());
357
358 if (v < 0) {
359 // in baseDir, but not in comparisonDir
360 drp->fResult = DiffRecord::kCouldNotCompare_Result;
361
362 basePath.append(*baseFiles[i]);
363 comparisonPath.append(*baseFiles[i]);
364
365 drp->fBase.fFilename = *baseFiles[i];
366 drp->fBase.fFullPath = basePath;
367 drp->fBase.fStatus = DiffResource::kExists_Status;
368
369 drp->fComparison.fFilename = *baseFiles[i];
370 drp->fComparison.fFullPath = comparisonPath;
371 drp->fComparison.fStatus = DiffResource::kDoesNotExist_Status;
372
373 ++i;
374 } else if (v > 0) {
375 // in comparisonDir, but not in baseDir
376 drp->fResult = DiffRecord::kCouldNotCompare_Result;
377
378 basePath.append(*comparisonFiles[j]);
379 comparisonPath.append(*comparisonFiles[j]);
380
381 drp->fBase.fFilename = *comparisonFiles[j];
382 drp->fBase.fFullPath = basePath;
383 drp->fBase.fStatus = DiffResource::kDoesNotExist_Status;
384
385 drp->fComparison.fFilename = *comparisonFiles[j];
386 drp->fComparison.fFullPath = comparisonPath;
387 drp->fComparison.fStatus = DiffResource::kExists_Status;
388
389 ++j;
390 } else {
391 // Found the same filename in both baseDir and comparisonDir.
392 SkASSERT(DiffRecord::kUnknown_Result == drp->fResult);
393
394 basePath.append(*baseFiles[i]);
395 comparisonPath.append(*comparisonFiles[j]);
396
397 drp->fBase.fFilename = *baseFiles[i];
398 drp->fBase.fFullPath = basePath;
399 drp->fBase.fStatus = DiffResource::kExists_Status;
400
401 drp->fComparison.fFilename = *comparisonFiles[j];
402 drp->fComparison.fFullPath = comparisonPath;
403 drp->fComparison.fStatus = DiffResource::kExists_Status;
404
405 SkAutoDataUnref baseFileBits(read_file(drp->fBase.fFullPath.c_str()));
406 if (NULL != baseFileBits) {
407 drp->fBase.fStatus = DiffResource::kRead_Status;
408 }
409 SkAutoDataUnref comparisonFileBits(read_file(drp->fComparison.fFullPath.c_str()));
410 if (NULL != comparisonFileBits) {
411 drp->fComparison.fStatus = DiffResource::kRead_Status;
412 }
413 if (NULL == baseFileBits || NULL == comparisonFileBits) {
414 if (NULL == baseFileBits) {
415 drp->fBase.fStatus = DiffResource::kCouldNotRead_Status;
416 }
417 if (NULL == comparisonFileBits) {
418 drp->fComparison.fStatus = DiffResource::kCouldNotRead_Status;
419 }
420 drp->fResult = DiffRecord::kCouldNotCompare_Result;
421
422 } else if (are_buffers_equal(baseFileBits, comparisonFileBits)) {
423 drp->fResult = DiffRecord::kEqualBits_Result;
424
425 } else {
426 AutoReleasePixels arp(drp);
427 get_bitmap(baseFileBits, drp->fBase, SkImageDecoder::kDecodePixels_Mode);
428 get_bitmap(comparisonFileBits, drp->fComparison,
429 SkImageDecoder::kDecodePixels_Mode);
430 if (DiffResource::kDecoded_Status == drp->fBase.fStatus &&
431 DiffResource::kDecoded_Status == drp->fComparison.fStatus) {
432 create_and_write_diff_image(drp, dmp, colorThreshold,
433 outputDir, drp->fBase.fFilename);
434 } else {
435 drp->fResult = DiffRecord::kCouldNotCompare_Result;
436 }
437 }
438
439 ++i;
440 ++j;
441 }
442
443 if (getBounds) {
444 get_bounds(*drp);
445 }
446 SkASSERT(DiffRecord::kUnknown_Result != drp->fResult);
447 differences->push(drp);
448 summary->add(drp);
449 }
450
451 for (; i < baseFiles.count(); ++i) {
452 // files only in baseDir
453 DiffRecord *drp = new DiffRecord();
454 drp->fBase.fFilename = *baseFiles[i];
455 drp->fBase.fFullPath = baseDir;
456 drp->fBase.fFullPath.append(drp->fBase.fFilename);
457 drp->fBase.fStatus = DiffResource::kExists_Status;
458
459 drp->fComparison.fFilename = *baseFiles[i];
460 drp->fComparison.fFullPath = comparisonDir;
461 drp->fComparison.fFullPath.append(drp->fComparison.fFilename);
462 drp->fComparison.fStatus = DiffResource::kDoesNotExist_Status;
463
464 drp->fResult = DiffRecord::kCouldNotCompare_Result;
465 if (getBounds) {
466 get_bounds(*drp);
467 }
468 differences->push(drp);
469 summary->add(drp);
470 }
471
472 for (; j < comparisonFiles.count(); ++j) {
473 // files only in comparisonDir
474 DiffRecord *drp = new DiffRecord();
475 drp->fBase.fFilename = *comparisonFiles[j];
476 drp->fBase.fFullPath = baseDir;
477 drp->fBase.fFullPath.append(drp->fBase.fFilename);
478 drp->fBase.fStatus = DiffResource::kDoesNotExist_Status;
479
480 drp->fComparison.fFilename = *comparisonFiles[j];
481 drp->fComparison.fFullPath = comparisonDir;
482 drp->fComparison.fFullPath.append(drp->fComparison.fFilename);
483 drp->fComparison.fStatus = DiffResource::kExists_Status;
484
485 drp->fResult = DiffRecord::kCouldNotCompare_Result;
486 if (getBounds) {
487 get_bounds(*drp);
488 }
489 differences->push(drp);
490 summary->add(drp);
491 }
492
493 release_file_list(&baseFiles);
494 release_file_list(&comparisonFiles);
495 }
496
usage(char * argv0)497 static void usage (char * argv0) {
498 SkDebugf("Skia baseline image diff tool\n");
499 SkDebugf("\n"
500 "Usage: \n"
501 " %s <baseDir> <comparisonDir> [outputDir] \n", argv0);
502 SkDebugf(
503 "\nArguments:"
504 "\n --failonresult <result>: After comparing all file pairs, exit with nonzero"
505 "\n return code (number of file pairs yielding this"
506 "\n result) if any file pairs yielded this result."
507 "\n This flag may be repeated, in which case the"
508 "\n return code will be the number of fail pairs"
509 "\n yielding ANY of these results."
510 "\n --failonstatus <baseStatus> <comparisonStatus>: exit with nonzero return"
511 "\n code if any file pairs yielded this status."
512 "\n --help: display this info"
513 "\n --listfilenames: list all filenames for each result type in stdout"
514 "\n --match <substring>: compare files whose filenames contain this substring;"
515 "\n if unspecified, compare ALL files."
516 "\n this flag may be repeated."
517 "\n --nodiffs: don't write out image diffs or index.html, just generate"
518 "\n report on stdout"
519 "\n --nomatch <substring>: regardless of --match, DO NOT compare files whose"
520 "\n filenames contain this substring."
521 "\n this flag may be repeated."
522 "\n --noprintdirs: do not print the directories used."
523 "\n --norecurse: do not recurse into subdirectories."
524 "\n --sortbymaxmismatch: sort by worst color channel mismatch;"
525 "\n break ties with -sortbymismatch"
526 "\n --sortbymismatch: sort by average color channel mismatch"
527 "\n --threshold <n>: only report differences > n (per color channel) [default 0]"
528 "\n --weighted: sort by # pixels different weighted by color difference"
529 "\n"
530 "\n baseDir: directory to read baseline images from."
531 "\n comparisonDir: directory to read comparison images from"
532 "\n outputDir: directory to write difference images and index.html to;"
533 "\n defaults to comparisonDir"
534 "\n"
535 "\nIf no sort is specified, it will sort by fraction of pixels mismatching."
536 "\n");
537 }
538
539 const int kNoError = 0;
540 const int kGenericError = -1;
541
542 int tool_main(int argc, char** argv);
tool_main(int argc,char ** argv)543 int tool_main(int argc, char** argv) {
544 DiffMetricProc diffProc = compute_diff_pmcolor;
545 int (*sortProc)(const void*, const void*) = compare<CompareDiffMetrics>;
546
547 // Maximum error tolerated in any one color channel in any one pixel before
548 // a difference is reported.
549 int colorThreshold = 0;
550 SkString baseDir;
551 SkString comparisonDir;
552 SkString outputDir;
553
554 StringArray matchSubstrings;
555 StringArray nomatchSubstrings;
556
557 bool generateDiffs = true;
558 bool listFilenames = false;
559 bool printDirNames = true;
560 bool recurseIntoSubdirs = true;
561
562 RecordArray differences;
563 DiffSummary summary;
564
565 bool failOnResultType[DiffRecord::kResultCount];
566 for (int i = 0; i < DiffRecord::kResultCount; i++) {
567 failOnResultType[i] = false;
568 }
569
570 bool failOnStatusType[DiffResource::kStatusCount][DiffResource::kStatusCount];
571 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
572 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
573 failOnStatusType[base][comparison] = false;
574 }
575 }
576
577 int i;
578 int numUnflaggedArguments = 0;
579 for (i = 1; i < argc; i++) {
580 if (!strcmp(argv[i], "--failonresult")) {
581 if (argc == ++i) {
582 SkDebugf("failonresult expects one argument.\n");
583 continue;
584 }
585 DiffRecord::Result type = DiffRecord::getResultByName(argv[i]);
586 if (type != DiffRecord::kResultCount) {
587 failOnResultType[type] = true;
588 } else {
589 SkDebugf("ignoring unrecognized result <%s>\n", argv[i]);
590 }
591 continue;
592 }
593 if (!strcmp(argv[i], "--failonstatus")) {
594 if (argc == ++i) {
595 SkDebugf("failonstatus missing base status.\n");
596 continue;
597 }
598 bool baseStatuses[DiffResource::kStatusCount];
599 if (!DiffResource::getMatchingStatuses(argv[i], baseStatuses)) {
600 SkDebugf("unrecognized base status <%s>\n", argv[i]);
601 }
602
603 if (argc == ++i) {
604 SkDebugf("failonstatus missing comparison status.\n");
605 continue;
606 }
607 bool comparisonStatuses[DiffResource::kStatusCount];
608 if (!DiffResource::getMatchingStatuses(argv[i], comparisonStatuses)) {
609 SkDebugf("unrecognized comarison status <%s>\n", argv[i]);
610 }
611
612 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
613 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
614 failOnStatusType[base][comparison] |=
615 baseStatuses[base] && comparisonStatuses[comparison];
616 }
617 }
618 continue;
619 }
620 if (!strcmp(argv[i], "--help")) {
621 usage(argv[0]);
622 return kNoError;
623 }
624 if (!strcmp(argv[i], "--listfilenames")) {
625 listFilenames = true;
626 continue;
627 }
628 if (!strcmp(argv[i], "--match")) {
629 matchSubstrings.push(new SkString(argv[++i]));
630 continue;
631 }
632 if (!strcmp(argv[i], "--nodiffs")) {
633 generateDiffs = false;
634 continue;
635 }
636 if (!strcmp(argv[i], "--nomatch")) {
637 nomatchSubstrings.push(new SkString(argv[++i]));
638 continue;
639 }
640 if (!strcmp(argv[i], "--noprintdirs")) {
641 printDirNames = false;
642 continue;
643 }
644 if (!strcmp(argv[i], "--norecurse")) {
645 recurseIntoSubdirs = false;
646 continue;
647 }
648 if (!strcmp(argv[i], "--sortbymaxmismatch")) {
649 sortProc = compare<CompareDiffMaxMismatches>;
650 continue;
651 }
652 if (!strcmp(argv[i], "--sortbymismatch")) {
653 sortProc = compare<CompareDiffMeanMismatches>;
654 continue;
655 }
656 if (!strcmp(argv[i], "--threshold")) {
657 colorThreshold = atoi(argv[++i]);
658 continue;
659 }
660 if (!strcmp(argv[i], "--weighted")) {
661 sortProc = compare<CompareDiffWeighted>;
662 continue;
663 }
664 if (argv[i][0] != '-') {
665 switch (numUnflaggedArguments++) {
666 case 0:
667 baseDir.set(argv[i]);
668 continue;
669 case 1:
670 comparisonDir.set(argv[i]);
671 continue;
672 case 2:
673 outputDir.set(argv[i]);
674 continue;
675 default:
676 SkDebugf("extra unflagged argument <%s>\n", argv[i]);
677 usage(argv[0]);
678 return kGenericError;
679 }
680 }
681
682 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
683 usage(argv[0]);
684 return kGenericError;
685 }
686
687 if (numUnflaggedArguments == 2) {
688 outputDir = comparisonDir;
689 } else if (numUnflaggedArguments != 3) {
690 usage(argv[0]);
691 return kGenericError;
692 }
693
694 if (!baseDir.endsWith(PATH_DIV_STR)) {
695 baseDir.append(PATH_DIV_STR);
696 }
697 if (printDirNames) {
698 printf("baseDir is [%s]\n", baseDir.c_str());
699 }
700
701 if (!comparisonDir.endsWith(PATH_DIV_STR)) {
702 comparisonDir.append(PATH_DIV_STR);
703 }
704 if (printDirNames) {
705 printf("comparisonDir is [%s]\n", comparisonDir.c_str());
706 }
707
708 if (!outputDir.endsWith(PATH_DIV_STR)) {
709 outputDir.append(PATH_DIV_STR);
710 }
711 if (generateDiffs) {
712 if (printDirNames) {
713 printf("writing diffs to outputDir is [%s]\n", outputDir.c_str());
714 }
715 } else {
716 if (printDirNames) {
717 printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str());
718 }
719 outputDir.set("");
720 }
721
722 // If no matchSubstrings were specified, match ALL strings
723 // (except for whatever nomatchSubstrings were specified, if any).
724 if (matchSubstrings.isEmpty()) {
725 matchSubstrings.push(new SkString(""));
726 }
727
728 create_diff_images(diffProc, colorThreshold, &differences,
729 baseDir, comparisonDir, outputDir,
730 matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, generateDiffs,
731 &summary);
732 summary.print(listFilenames, failOnResultType, failOnStatusType);
733
734 if (differences.count()) {
735 qsort(differences.begin(), differences.count(),
736 sizeof(DiffRecord*), sortProc);
737 }
738
739 if (generateDiffs) {
740 print_diff_page(summary.fNumMatches, colorThreshold, differences,
741 baseDir, comparisonDir, outputDir);
742 }
743
744 for (i = 0; i < differences.count(); i++) {
745 delete differences[i];
746 }
747 matchSubstrings.deleteAll();
748 nomatchSubstrings.deleteAll();
749
750 int num_failing_results = 0;
751 for (int i = 0; i < DiffRecord::kResultCount; i++) {
752 if (failOnResultType[i]) {
753 num_failing_results += summary.fResultsOfType[i].count();
754 }
755 }
756 if (!failOnResultType[DiffRecord::kCouldNotCompare_Result]) {
757 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
758 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
759 if (failOnStatusType[base][comparison]) {
760 num_failing_results += summary.fStatusOfType[base][comparison].count();
761 }
762 }
763 }
764 }
765
766 // On Linux (and maybe other platforms too), any results outside of the
767 // range [0...255] are wrapped (mod 256). Do the conversion ourselves, to
768 // make sure that we only return 0 when there were no failures.
769 return (num_failing_results > 255) ? 255 : num_failing_results;
770 }
771
772 #if !defined SK_BUILD_FOR_IOS
main(int argc,char * const argv[])773 int main(int argc, char * const argv[]) {
774 return tool_main(argc, (char**) argv);
775 }
776 #endif
777