2 * Copyright 2011 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
8 #include "skdiff_html.h"
9 #include "skdiff_utils.h"
12 #include "SkForceLinking.h"
13 #include "SkImageDecoder.h"
14 #include "SkImageEncoder.h"
17 #include "SkTDArray.h"
18 #include "SkTemplates.h"
19 #include "SkTSearch.h"
21 __SK_FORCE_IMAGE_DECODER_LINKING;
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.
30 * Creates an index.html in the current third directory to compare each
31 * pair that does not match exactly.
32 * Recursively descends directories, unless run with --norecurse.
34 * Returns zero exit code if all images match across baseDir and comparisonDir.
37 typedef SkTDArray<SkString*> StringArray;
38 typedef StringArray FileArray;
40 static void add_unique_basename(StringArray* array, const SkString& filename) {
42 const char* src = filename.c_str();
43 const char* trimmed = strrchr(src, SkPATH_SEPARATOR);
45 trimmed += 1; // skip the separator
49 const char* end = strrchr(trimmed, '.');
51 end = trimmed + strlen(trimmed);
53 SkString result(trimmed, end - trimmed);
55 // only add unique entries
56 for (int i = 0; i < array->count(); ++i) {
57 if (*array->getAt(i) == result) {
61 *array->append() = new SkString(result);
69 , fMaxMismatchPercent(0) { };
72 for (int i = 0; i < DiffRecord::kResultCount; ++i) {
73 fResultsOfType[i].deleteAll();
75 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
76 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
77 fStatusOfType[base][comparison].deleteAll();
83 uint32_t fNumMismatches;
84 uint32_t fMaxMismatchV;
85 float fMaxMismatchPercent;
87 FileArray fResultsOfType[DiffRecord::kResultCount];
88 FileArray fStatusOfType[DiffResource::kStatusCount][DiffResource::kStatusCount];
90 StringArray fFailedBaseNames[DiffRecord::kResultCount];
92 void printContents(const FileArray& fileArray,
93 const char* baseStatus, const char* comparisonStatus,
95 int n = fileArray.count();
96 printf("%d file pairs %s in baseDir and %s in comparisonDir",
97 n, baseStatus, comparisonStatus);
100 for (int i = 0; i < n; ++i) {
101 printf("%s ", fileArray[i]->c_str());
107 void printStatus(bool listFilenames,
108 bool failOnStatusType[DiffResource::kStatusCount]
109 [DiffResource::kStatusCount]) {
110 typedef DiffResource::Status Status;
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]) {
123 printContents(fileArray,
124 DiffResource::getStatusDescription(baseStatus),
125 DiffResource::getStatusDescription(comparisonStatus),
132 // Print a line about the contents of this FileArray to stdout.
133 void printContents(const FileArray& fileArray, const char* headerText, bool listFilenames) {
134 int n = fileArray.count();
135 printf("%d file pairs %s", n, headerText);
138 for (int i = 0; i < n; ++i) {
139 printf("%s ", fileArray[i]->c_str());
145 void print(bool listFilenames, bool failOnResultType[DiffRecord::kResultCount],
146 bool failOnStatusType[DiffResource::kStatusCount]
147 [DiffResource::kStatusCount]) {
148 printf("\ncompared %d file pairs:\n", fNumMatches + fNumMismatches);
149 for (int resultInt = 0; resultInt < DiffRecord::kResultCount; ++resultInt) {
150 DiffRecord::Result result = static_cast<DiffRecord::Result>(resultInt);
151 if (failOnResultType[result]) {
156 printContents(fResultsOfType[result], DiffRecord::getResultDescription(result),
158 if (DiffRecord::kCouldNotCompare_Result == result) {
159 printStatus(listFilenames, failOnStatusType);
162 printf("(results marked with [*] will cause nonzero return value)\n");
163 printf("\nnumber of mismatching file pairs: %d\n", fNumMismatches);
164 if (fNumMismatches > 0) {
165 printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
166 printf("Largest area mismatch was %.2f%% of pixels\n",fMaxMismatchPercent);
170 void printfFailingBaseNames(const char separator[]) {
171 for (int resultInt = 0; resultInt < DiffRecord::kResultCount; ++resultInt) {
172 const StringArray& array = fFailedBaseNames[resultInt];
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);
183 void add (DiffRecord* drp) {
184 uint32_t mismatchValue;
186 if (drp->fBase.fFilename.equals(drp->fComparison.fFilename)) {
187 fResultsOfType[drp->fResult].push(new SkString(drp->fBase.fFilename));
189 SkString* blame = new SkString("(");
190 blame->append(drp->fBase.fFilename);
192 blame->append(drp->fComparison.fFilename);
194 fResultsOfType[drp->fResult].push(blame);
196 switch (drp->fResult) {
197 case DiffRecord::kEqualBits_Result:
200 case DiffRecord::kEqualPixels_Result:
203 case DiffRecord::kDifferentSizes_Result:
206 case DiffRecord::kDifferentPixels_Result:
208 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
209 fMaxMismatchPercent = drp->fFractionDifference * 100;
211 mismatchValue = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
213 if (mismatchValue > fMaxMismatchV) {
214 fMaxMismatchV = mismatchValue;
217 case DiffRecord::kCouldNotCompare_Result:
219 fStatusOfType[drp->fBase.fStatus][drp->fComparison.fStatus].push(
220 new SkString(drp->fBase.fFilename));
222 case DiffRecord::kUnknown_Result:
223 SkDEBUGFAIL("adding uncategorized DiffRecord");
226 SkDEBUGFAIL("adding DiffRecord with unhandled fResult value");
230 switch (drp->fResult) {
231 case DiffRecord::kEqualBits_Result:
232 case DiffRecord::kEqualPixels_Result:
235 add_unique_basename(&fFailedBaseNames[drp->fResult], drp->fBase.fFilename);
241 /// Returns true if string contains any of these substrings.
242 static 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())) {
252 /// Internal (potentially recursive) implementation of get_file_list.
253 static 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);
264 // Iterate over files (not directories) within dir.
265 SkOSFile::Iter fileIterator(dir.c_str());
267 while (fileIterator.next(&fileName, false)) {
268 if (fileName.startsWith(".")) {
271 SkString pathRelativeToRootDir(subDir);
272 if (!isSubDirEmpty) {
273 pathRelativeToRootDir.append(PATH_DIV_STR);
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));
282 // Recurse into any non-ignored subdirectories.
283 if (recurseIntoSubdirs) {
284 SkOSFile::Iter dirIterator(dir.c_str());
286 while (dirIterator.next(&dirName, true)) {
287 if (dirName.startsWith(".")) {
290 SkString pathRelativeToRootDir(subDir);
291 if (!isSubDirEmpty) {
292 pathRelativeToRootDir.append(PATH_DIV_STR);
294 pathRelativeToRootDir.append(dirName);
295 if (!string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) {
296 get_file_list_subdir(rootDir, pathRelativeToRootDir,
297 matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
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.
309 static void get_file_list(const SkString& dir,
310 const StringArray& matchSubstrings,
311 const StringArray& nomatchSubstrings,
312 bool recurseIntoSubdirs, FileArray *files) {
313 get_file_list_subdir(dir, SkString(""),
314 matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
318 static void release_file_list(FileArray *files) {
322 /// Comparison routines for qsort, sort by file names.
323 static int compare_file_name_metrics(SkString **lhs, SkString **rhs) {
324 return strcmp((*lhs)->c_str(), (*rhs)->c_str());
327 class AutoReleasePixels {
329 AutoReleasePixels(DiffRecord* drp)
331 SkASSERT(drp != NULL);
333 ~AutoReleasePixels() {
334 fDrp->fBase.fBitmap.setPixelRef(NULL);
335 fDrp->fComparison.fBitmap.setPixelRef(NULL);
336 fDrp->fDifference.fBitmap.setPixelRef(NULL);
337 fDrp->fWhite.fBitmap.setPixelRef(NULL);
344 static 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()));
347 if (NULL == fileBits) {
348 SkDebugf("WARNING: couldn't read %s file <%s>\n", name, resource.fFullPath.c_str());
349 resource.fStatus = DiffResource::kCouldNotRead_Status;
351 get_bitmap(fileBits, resource, SkImageDecoder::kDecodeBounds_Mode);
356 static void get_bounds(DiffRecord& drp) {
357 get_bounds(drp.fBase, "base");
358 get_bounds(drp.fComparison, "comparison");
362 #define ANSI_COLOR_RED ""
363 #define ANSI_COLOR_GREEN ""
364 #define ANSI_COLOR_YELLOW ""
365 #define ANSI_COLOR_RESET ""
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"
373 #define VERBOSE_STATUS(status,color,filename) if (verbose) printf( "[ " color " %10s " ANSI_COLOR_RESET " ] %s\n", status, filename->c_str())
375 /// Creates difference images, returns the number that have a 0 metric.
376 /// If outputDir.isEmpty(), don't write out diff files.
377 static void create_diff_images (DiffMetricProc dmp,
378 const int colorThreshold,
379 RecordArray* differences,
380 const SkString& baseDir,
381 const SkString& comparisonDir,
382 const SkString& outputDir,
383 const StringArray& matchSubstrings,
384 const StringArray& nomatchSubstrings,
385 bool recurseIntoSubdirs,
388 DiffSummary* summary) {
389 SkASSERT(!baseDir.isEmpty());
390 SkASSERT(!comparisonDir.isEmpty());
393 FileArray comparisonFiles;
395 get_file_list(baseDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, &baseFiles);
396 get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
399 if (!baseFiles.isEmpty()) {
400 qsort(baseFiles.begin(), baseFiles.count(), sizeof(SkString*),
401 SkCastForQSort(compare_file_name_metrics));
403 if (!comparisonFiles.isEmpty()) {
404 qsort(comparisonFiles.begin(), comparisonFiles.count(),
405 sizeof(SkString*), SkCastForQSort(compare_file_name_metrics));
411 while (i < baseFiles.count() &&
412 j < comparisonFiles.count()) {
414 SkString basePath(baseDir);
415 SkString comparisonPath(comparisonDir);
417 DiffRecord *drp = new DiffRecord;
418 int v = strcmp(baseFiles[i]->c_str(), comparisonFiles[j]->c_str());
421 // in baseDir, but not in comparisonDir
422 drp->fResult = DiffRecord::kCouldNotCompare_Result;
424 basePath.append(*baseFiles[i]);
425 comparisonPath.append(*baseFiles[i]);
427 drp->fBase.fFilename = *baseFiles[i];
428 drp->fBase.fFullPath = basePath;
429 drp->fBase.fStatus = DiffResource::kExists_Status;
431 drp->fComparison.fFilename = *baseFiles[i];
432 drp->fComparison.fFullPath = comparisonPath;
433 drp->fComparison.fStatus = DiffResource::kDoesNotExist_Status;
435 VERBOSE_STATUS("MISSING", ANSI_COLOR_YELLOW, baseFiles[i]);
439 // in comparisonDir, but not in baseDir
440 drp->fResult = DiffRecord::kCouldNotCompare_Result;
442 basePath.append(*comparisonFiles[j]);
443 comparisonPath.append(*comparisonFiles[j]);
445 drp->fBase.fFilename = *comparisonFiles[j];
446 drp->fBase.fFullPath = basePath;
447 drp->fBase.fStatus = DiffResource::kDoesNotExist_Status;
449 drp->fComparison.fFilename = *comparisonFiles[j];
450 drp->fComparison.fFullPath = comparisonPath;
451 drp->fComparison.fStatus = DiffResource::kExists_Status;
453 VERBOSE_STATUS("MISSING", ANSI_COLOR_YELLOW, comparisonFiles[j]);
457 // Found the same filename in both baseDir and comparisonDir.
458 SkASSERT(DiffRecord::kUnknown_Result == drp->fResult);
460 basePath.append(*baseFiles[i]);
461 comparisonPath.append(*comparisonFiles[j]);
463 drp->fBase.fFilename = *baseFiles[i];
464 drp->fBase.fFullPath = basePath;
465 drp->fBase.fStatus = DiffResource::kExists_Status;
467 drp->fComparison.fFilename = *comparisonFiles[j];
468 drp->fComparison.fFullPath = comparisonPath;
469 drp->fComparison.fStatus = DiffResource::kExists_Status;
471 SkAutoDataUnref baseFileBits(read_file(drp->fBase.fFullPath.c_str()));
473 drp->fBase.fStatus = DiffResource::kRead_Status;
475 SkAutoDataUnref comparisonFileBits(read_file(drp->fComparison.fFullPath.c_str()));
476 if (comparisonFileBits) {
477 drp->fComparison.fStatus = DiffResource::kRead_Status;
479 if (NULL == baseFileBits || NULL == comparisonFileBits) {
480 if (NULL == baseFileBits) {
481 drp->fBase.fStatus = DiffResource::kCouldNotRead_Status;
482 VERBOSE_STATUS("READ FAIL", ANSI_COLOR_RED, baseFiles[i]);
484 if (NULL == comparisonFileBits) {
485 drp->fComparison.fStatus = DiffResource::kCouldNotRead_Status;
486 VERBOSE_STATUS("READ FAIL", ANSI_COLOR_RED, comparisonFiles[j]);
488 drp->fResult = DiffRecord::kCouldNotCompare_Result;
490 } else if (are_buffers_equal(baseFileBits, comparisonFileBits)) {
491 drp->fResult = DiffRecord::kEqualBits_Result;
492 VERBOSE_STATUS("MATCH", ANSI_COLOR_GREEN, baseFiles[i]);
494 AutoReleasePixels arp(drp);
495 get_bitmap(baseFileBits, drp->fBase, SkImageDecoder::kDecodePixels_Mode);
496 get_bitmap(comparisonFileBits, drp->fComparison,
497 SkImageDecoder::kDecodePixels_Mode);
498 VERBOSE_STATUS("DIFFERENT", ANSI_COLOR_RED, baseFiles[i]);
499 if (DiffResource::kDecoded_Status == drp->fBase.fStatus &&
500 DiffResource::kDecoded_Status == drp->fComparison.fStatus) {
501 create_and_write_diff_image(drp, dmp, colorThreshold,
502 outputDir, drp->fBase.fFilename);
504 drp->fResult = DiffRecord::kCouldNotCompare_Result;
515 SkASSERT(DiffRecord::kUnknown_Result != drp->fResult);
516 differences->push(drp);
520 for (; i < baseFiles.count(); ++i) {
521 // files only in baseDir
522 DiffRecord *drp = new DiffRecord();
523 drp->fBase.fFilename = *baseFiles[i];
524 drp->fBase.fFullPath = baseDir;
525 drp->fBase.fFullPath.append(drp->fBase.fFilename);
526 drp->fBase.fStatus = DiffResource::kExists_Status;
528 drp->fComparison.fFilename = *baseFiles[i];
529 drp->fComparison.fFullPath = comparisonDir;
530 drp->fComparison.fFullPath.append(drp->fComparison.fFilename);
531 drp->fComparison.fStatus = DiffResource::kDoesNotExist_Status;
533 drp->fResult = DiffRecord::kCouldNotCompare_Result;
537 differences->push(drp);
541 for (; j < comparisonFiles.count(); ++j) {
542 // files only in comparisonDir
543 DiffRecord *drp = new DiffRecord();
544 drp->fBase.fFilename = *comparisonFiles[j];
545 drp->fBase.fFullPath = baseDir;
546 drp->fBase.fFullPath.append(drp->fBase.fFilename);
547 drp->fBase.fStatus = DiffResource::kDoesNotExist_Status;
549 drp->fComparison.fFilename = *comparisonFiles[j];
550 drp->fComparison.fFullPath = comparisonDir;
551 drp->fComparison.fFullPath.append(drp->fComparison.fFilename);
552 drp->fComparison.fStatus = DiffResource::kExists_Status;
554 drp->fResult = DiffRecord::kCouldNotCompare_Result;
558 differences->push(drp);
562 release_file_list(&baseFiles);
563 release_file_list(&comparisonFiles);
566 static void usage (char * argv0) {
567 SkDebugf("Skia baseline image diff tool\n");
570 " %s <baseDir> <comparisonDir> [outputDir] \n", argv0);
573 "\n --failonresult <result>: After comparing all file pairs, exit with nonzero"
574 "\n return code (number of file pairs yielding this"
575 "\n result) if any file pairs yielded this result."
576 "\n This flag may be repeated, in which case the"
577 "\n return code will be the number of fail pairs"
578 "\n yielding ANY of these results."
579 "\n --failonstatus <baseStatus> <comparisonStatus>: exit with nonzero return"
580 "\n code if any file pairs yielded this status."
581 "\n --help: display this info"
582 "\n --listfilenames: list all filenames for each result type in stdout"
583 "\n --match <substring>: compare files whose filenames contain this substring;"
584 "\n if unspecified, compare ALL files."
585 "\n this flag may be repeated."
586 "\n --nodiffs: don't write out image diffs or index.html, just generate"
587 "\n report on stdout"
588 "\n --nomatch <substring>: regardless of --match, DO NOT compare files whose"
589 "\n filenames contain this substring."
590 "\n this flag may be repeated."
591 "\n --noprintdirs: do not print the directories used."
592 "\n --norecurse: do not recurse into subdirectories."
593 "\n --sortbymaxmismatch: sort by worst color channel mismatch;"
594 "\n break ties with -sortbymismatch"
595 "\n --sortbymismatch: sort by average color channel mismatch"
596 "\n --threshold <n>: only report differences > n (per color channel) [default 0]"
597 "\n --weighted: sort by # pixels different weighted by color difference"
599 "\n baseDir: directory to read baseline images from."
600 "\n comparisonDir: directory to read comparison images from"
601 "\n outputDir: directory to write difference images and index.html to;"
602 "\n defaults to comparisonDir"
604 "\nIf no sort is specified, it will sort by fraction of pixels mismatching."
608 const int kNoError = 0;
609 const int kGenericError = -1;
611 int tool_main(int argc, char** argv);
612 int tool_main(int argc, char** argv) {
613 DiffMetricProc diffProc = compute_diff_pmcolor;
614 int (*sortProc)(const void*, const void*) = compare<CompareDiffMetrics>;
616 // Maximum error tolerated in any one color channel in any one pixel before
617 // a difference is reported.
618 int colorThreshold = 0;
620 SkString comparisonDir;
623 StringArray matchSubstrings;
624 StringArray nomatchSubstrings;
626 bool generateDiffs = true;
627 bool listFilenames = false;
628 bool printDirNames = true;
629 bool recurseIntoSubdirs = true;
630 bool verbose = false;
631 bool listFailingBase = false;
633 RecordArray differences;
636 bool failOnResultType[DiffRecord::kResultCount];
637 for (int i = 0; i < DiffRecord::kResultCount; i++) {
638 failOnResultType[i] = false;
641 bool failOnStatusType[DiffResource::kStatusCount][DiffResource::kStatusCount];
642 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
643 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
644 failOnStatusType[base][comparison] = false;
649 int numUnflaggedArguments = 0;
650 for (i = 1; i < argc; i++) {
651 if (!strcmp(argv[i], "--failonresult")) {
653 SkDebugf("failonresult expects one argument.\n");
656 DiffRecord::Result type = DiffRecord::getResultByName(argv[i]);
657 if (type != DiffRecord::kResultCount) {
658 failOnResultType[type] = true;
660 SkDebugf("ignoring unrecognized result <%s>\n", argv[i]);
664 if (!strcmp(argv[i], "--failonstatus")) {
666 SkDebugf("failonstatus missing base status.\n");
669 bool baseStatuses[DiffResource::kStatusCount];
670 if (!DiffResource::getMatchingStatuses(argv[i], baseStatuses)) {
671 SkDebugf("unrecognized base status <%s>\n", argv[i]);
675 SkDebugf("failonstatus missing comparison status.\n");
678 bool comparisonStatuses[DiffResource::kStatusCount];
679 if (!DiffResource::getMatchingStatuses(argv[i], comparisonStatuses)) {
680 SkDebugf("unrecognized comarison status <%s>\n", argv[i]);
683 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
684 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
685 failOnStatusType[base][comparison] |=
686 baseStatuses[base] && comparisonStatuses[comparison];
691 if (!strcmp(argv[i], "--help")) {
695 if (!strcmp(argv[i], "--listfilenames")) {
696 listFilenames = true;
699 if (!strcmp(argv[i], "--verbose")) {
703 if (!strcmp(argv[i], "--match")) {
704 matchSubstrings.push(new SkString(argv[++i]));
707 if (!strcmp(argv[i], "--nodiffs")) {
708 generateDiffs = false;
711 if (!strcmp(argv[i], "--nomatch")) {
712 nomatchSubstrings.push(new SkString(argv[++i]));
715 if (!strcmp(argv[i], "--noprintdirs")) {
716 printDirNames = false;
719 if (!strcmp(argv[i], "--norecurse")) {
720 recurseIntoSubdirs = false;
723 if (!strcmp(argv[i], "--sortbymaxmismatch")) {
724 sortProc = compare<CompareDiffMaxMismatches>;
727 if (!strcmp(argv[i], "--sortbymismatch")) {
728 sortProc = compare<CompareDiffMeanMismatches>;
731 if (!strcmp(argv[i], "--threshold")) {
732 colorThreshold = atoi(argv[++i]);
735 if (!strcmp(argv[i], "--weighted")) {
736 sortProc = compare<CompareDiffWeighted>;
739 if (argv[i][0] != '-') {
740 switch (numUnflaggedArguments++) {
742 baseDir.set(argv[i]);
745 comparisonDir.set(argv[i]);
748 outputDir.set(argv[i]);
751 SkDebugf("extra unflagged argument <%s>\n", argv[i]);
753 return kGenericError;
756 if (!strcmp(argv[i], "--listFailingBase")) {
757 listFailingBase = true;
761 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
763 return kGenericError;
766 if (numUnflaggedArguments == 2) {
767 outputDir = comparisonDir;
768 } else if (numUnflaggedArguments != 3) {
770 return kGenericError;
773 if (!baseDir.endsWith(PATH_DIV_STR)) {
774 baseDir.append(PATH_DIV_STR);
777 printf("baseDir is [%s]\n", baseDir.c_str());
780 if (!comparisonDir.endsWith(PATH_DIV_STR)) {
781 comparisonDir.append(PATH_DIV_STR);
784 printf("comparisonDir is [%s]\n", comparisonDir.c_str());
787 if (!outputDir.endsWith(PATH_DIV_STR)) {
788 outputDir.append(PATH_DIV_STR);
792 printf("writing diffs to outputDir is [%s]\n", outputDir.c_str());
796 printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str());
801 // If no matchSubstrings were specified, match ALL strings
802 // (except for whatever nomatchSubstrings were specified, if any).
803 if (matchSubstrings.isEmpty()) {
804 matchSubstrings.push(new SkString(""));
807 create_diff_images(diffProc, colorThreshold, &differences,
808 baseDir, comparisonDir, outputDir,
809 matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, generateDiffs,
811 summary.print(listFilenames, failOnResultType, failOnStatusType);
813 if (listFailingBase) {
814 summary.printfFailingBaseNames("\n");
817 if (differences.count()) {
818 qsort(differences.begin(), differences.count(),
819 sizeof(DiffRecord*), sortProc);
823 print_diff_page(summary.fNumMatches, colorThreshold, differences,
824 baseDir, comparisonDir, outputDir);
827 for (i = 0; i < differences.count(); i++) {
828 delete differences[i];
830 matchSubstrings.deleteAll();
831 nomatchSubstrings.deleteAll();
833 int num_failing_results = 0;
834 for (int i = 0; i < DiffRecord::kResultCount; i++) {
835 if (failOnResultType[i]) {
836 num_failing_results += summary.fResultsOfType[i].count();
839 if (!failOnResultType[DiffRecord::kCouldNotCompare_Result]) {
840 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
841 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
842 if (failOnStatusType[base][comparison]) {
843 num_failing_results += summary.fStatusOfType[base][comparison].count();
849 // On Linux (and maybe other platforms too), any results outside of the
850 // range [0...255] are wrapped (mod 256). Do the conversion ourselves, to
851 // make sure that we only return 0 when there were no failures.
852 return (num_failing_results > 255) ? 255 : num_failing_results;
855 #if !defined SK_BUILD_FOR_IOS
856 int main(int argc, char * const argv[]) {
857 return tool_main(argc, (char**) argv);