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 "SkTSearch.h"
22 __SK_FORCE_IMAGE_DECODER_LINKING;
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.
35 * Returns zero exit code if all images match across baseDir and comparisonDir.
38 typedef SkTDArray<SkString*> StringArray;
39 typedef StringArray FileArray;
41 static void add_unique_basename(StringArray* array, const SkString& filename) {
43 const char* src = filename.c_str();
44 const char* trimmed = strrchr(src, SkPATH_SEPARATOR);
46 trimmed += 1; // skip the separator
50 const char* end = strrchr(trimmed, '.');
52 end = trimmed + strlen(trimmed);
54 SkString result(trimmed, end - trimmed);
56 // only add unique entries
57 for (int i = 0; i < array->count(); ++i) {
58 if (*array->getAt(i) == result) {
62 *array->append() = new SkString(result);
70 , fMaxMismatchPercent(0) { };
73 for (int i = 0; i < DiffRecord::kResultCount; ++i) {
74 fResultsOfType[i].deleteAll();
76 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
77 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
78 fStatusOfType[base][comparison].deleteAll();
84 uint32_t fNumMismatches;
85 uint32_t fMaxMismatchV;
86 float fMaxMismatchPercent;
88 FileArray fResultsOfType[DiffRecord::kResultCount];
89 FileArray fStatusOfType[DiffResource::kStatusCount][DiffResource::kStatusCount];
91 StringArray fFailedBaseNames[DiffRecord::kResultCount];
93 void printContents(const FileArray& fileArray,
94 const char* baseStatus, const char* comparisonStatus,
96 int n = fileArray.count();
97 printf("%d file pairs %s in baseDir and %s in comparisonDir",
98 n, baseStatus, comparisonStatus);
101 for (int i = 0; i < n; ++i) {
102 printf("%s ", fileArray[i]->c_str());
108 void printStatus(bool listFilenames,
109 bool failOnStatusType[DiffResource::kStatusCount]
110 [DiffResource::kStatusCount]) {
111 typedef DiffResource::Status Status;
113 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
114 Status baseStatus = static_cast<Status>(base);
115 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
116 Status comparisonStatus = static_cast<Status>(comparison);
117 const FileArray& fileArray = fStatusOfType[base][comparison];
118 if (fileArray.count() > 0) {
119 if (failOnStatusType[base][comparison]) {
124 printContents(fileArray,
125 DiffResource::getStatusDescription(baseStatus),
126 DiffResource::getStatusDescription(comparisonStatus),
133 // Print a line about the contents of this FileArray to stdout.
134 void printContents(const FileArray& fileArray, const char* headerText, bool listFilenames) {
135 int n = fileArray.count();
136 printf("%d file pairs %s", n, headerText);
139 for (int i = 0; i < n; ++i) {
140 printf("%s ", fileArray[i]->c_str());
146 void print(bool listFilenames, bool failOnResultType[DiffRecord::kResultCount],
147 bool failOnStatusType[DiffResource::kStatusCount]
148 [DiffResource::kStatusCount]) {
149 printf("\ncompared %d file pairs:\n", fNumMatches + fNumMismatches);
150 for (int resultInt = 0; resultInt < DiffRecord::kResultCount; ++resultInt) {
151 DiffRecord::Result result = static_cast<DiffRecord::Result>(resultInt);
152 if (failOnResultType[result]) {
157 printContents(fResultsOfType[result], DiffRecord::getResultDescription(result),
159 if (DiffRecord::kCouldNotCompare_Result == result) {
160 printStatus(listFilenames, failOnStatusType);
163 printf("(results marked with [*] will cause nonzero return value)\n");
164 printf("\nnumber of mismatching file pairs: %d\n", fNumMismatches);
165 if (fNumMismatches > 0) {
166 printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
167 printf("Largest area mismatch was %.2f%% of pixels\n",fMaxMismatchPercent);
171 void printfFailingBaseNames(const char separator[]) {
172 for (int resultInt = 0; resultInt < DiffRecord::kResultCount; ++resultInt) {
173 const StringArray& array = fFailedBaseNames[resultInt];
175 printf("%s [%d]%s", DiffRecord::ResultNames[resultInt], array.count(), separator);
176 for (int j = 0; j < array.count(); ++j) {
177 printf("%s%s", array[j]->c_str(), separator);
184 void add (DiffRecord* drp) {
185 uint32_t mismatchValue;
187 if (drp->fBase.fFilename.equals(drp->fComparison.fFilename)) {
188 fResultsOfType[drp->fResult].push(new SkString(drp->fBase.fFilename));
190 SkString* blame = new SkString("(");
191 blame->append(drp->fBase.fFilename);
193 blame->append(drp->fComparison.fFilename);
195 fResultsOfType[drp->fResult].push(blame);
197 switch (drp->fResult) {
198 case DiffRecord::kEqualBits_Result:
201 case DiffRecord::kEqualPixels_Result:
204 case DiffRecord::kDifferentSizes_Result:
207 case DiffRecord::kDifferentPixels_Result:
209 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
210 fMaxMismatchPercent = drp->fFractionDifference * 100;
212 mismatchValue = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
214 if (mismatchValue > fMaxMismatchV) {
215 fMaxMismatchV = mismatchValue;
218 case DiffRecord::kCouldNotCompare_Result:
220 fStatusOfType[drp->fBase.fStatus][drp->fComparison.fStatus].push(
221 new SkString(drp->fBase.fFilename));
223 case DiffRecord::kUnknown_Result:
224 SkDEBUGFAIL("adding uncategorized DiffRecord");
227 SkDEBUGFAIL("adding DiffRecord with unhandled fResult value");
231 switch (drp->fResult) {
232 case DiffRecord::kEqualBits_Result:
233 case DiffRecord::kEqualPixels_Result:
236 add_unique_basename(&fFailedBaseNames[drp->fResult], drp->fBase.fFilename);
242 /// Returns true if string contains any of these substrings.
243 static bool string_contains_any_of(const SkString& string,
244 const StringArray& substrings) {
245 for (int i = 0; i < substrings.count(); i++) {
246 if (string.contains(substrings[i]->c_str())) {
253 /// Internal (potentially recursive) implementation of get_file_list.
254 static void get_file_list_subdir(const SkString& rootDir, const SkString& subDir,
255 const StringArray& matchSubstrings,
256 const StringArray& nomatchSubstrings,
257 bool recurseIntoSubdirs, FileArray *files) {
258 bool isSubDirEmpty = subDir.isEmpty();
259 SkString dir(rootDir);
260 if (!isSubDirEmpty) {
261 dir.append(PATH_DIV_STR);
265 // Iterate over files (not directories) within dir.
266 SkOSFile::Iter fileIterator(dir.c_str());
268 while (fileIterator.next(&fileName, false)) {
269 if (fileName.startsWith(".")) {
272 SkString pathRelativeToRootDir(subDir);
273 if (!isSubDirEmpty) {
274 pathRelativeToRootDir.append(PATH_DIV_STR);
276 pathRelativeToRootDir.append(fileName);
277 if (string_contains_any_of(pathRelativeToRootDir, matchSubstrings) &&
278 !string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) {
279 files->push(new SkString(pathRelativeToRootDir));
283 // Recurse into any non-ignored subdirectories.
284 if (recurseIntoSubdirs) {
285 SkOSFile::Iter dirIterator(dir.c_str());
287 while (dirIterator.next(&dirName, true)) {
288 if (dirName.startsWith(".")) {
291 SkString pathRelativeToRootDir(subDir);
292 if (!isSubDirEmpty) {
293 pathRelativeToRootDir.append(PATH_DIV_STR);
295 pathRelativeToRootDir.append(dirName);
296 if (!string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) {
297 get_file_list_subdir(rootDir, pathRelativeToRootDir,
298 matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
305 /// Iterate over dir and get all files whose filename:
306 /// - matches any of the substrings in matchSubstrings, but...
307 /// - DOES NOT match any of the substrings in nomatchSubstrings
308 /// - DOES NOT start with a dot (.)
309 /// Adds the matching files to the list in *files.
310 static void get_file_list(const SkString& dir,
311 const StringArray& matchSubstrings,
312 const StringArray& nomatchSubstrings,
313 bool recurseIntoSubdirs, FileArray *files) {
314 get_file_list_subdir(dir, SkString(""),
315 matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
319 static void release_file_list(FileArray *files) {
323 /// Comparison routines for qsort, sort by file names.
324 static int compare_file_name_metrics(SkString **lhs, SkString **rhs) {
325 return strcmp((*lhs)->c_str(), (*rhs)->c_str());
328 class AutoReleasePixels {
330 AutoReleasePixels(DiffRecord* drp)
332 SkASSERT(drp != nullptr);
334 ~AutoReleasePixels() {
335 fDrp->fBase.fBitmap.setPixelRef(nullptr);
336 fDrp->fComparison.fBitmap.setPixelRef(nullptr);
337 fDrp->fDifference.fBitmap.setPixelRef(nullptr);
338 fDrp->fWhite.fBitmap.setPixelRef(nullptr);
345 static void get_bounds(DiffResource& resource, const char* name) {
346 if (resource.fBitmap.empty() && !DiffResource::isStatusFailed(resource.fStatus)) {
347 SkAutoDataUnref fileBits(read_file(resource.fFullPath.c_str()));
348 if (nullptr == fileBits) {
349 SkDebugf("WARNING: couldn't read %s file <%s>\n", name, resource.fFullPath.c_str());
350 resource.fStatus = DiffResource::kCouldNotRead_Status;
352 get_bitmap(fileBits, resource, SkImageDecoder::kDecodeBounds_Mode);
357 static void get_bounds(DiffRecord& drp) {
358 get_bounds(drp.fBase, "base");
359 get_bounds(drp.fComparison, "comparison");
363 #define ANSI_COLOR_RED ""
364 #define ANSI_COLOR_GREEN ""
365 #define ANSI_COLOR_YELLOW ""
366 #define ANSI_COLOR_RESET ""
368 #define ANSI_COLOR_RED "\x1b[31m"
369 #define ANSI_COLOR_GREEN "\x1b[32m"
370 #define ANSI_COLOR_YELLOW "\x1b[33m"
371 #define ANSI_COLOR_RESET "\x1b[0m"
374 #define VERBOSE_STATUS(status,color,filename) if (verbose) printf( "[ " color " %10s " ANSI_COLOR_RESET " ] %s\n", status, filename->c_str())
376 /// Creates difference images, returns the number that have a 0 metric.
377 /// If outputDir.isEmpty(), don't write out diff files.
378 static void create_diff_images (DiffMetricProc dmp,
379 const int colorThreshold,
380 RecordArray* differences,
381 const SkString& baseDir,
382 const SkString& comparisonDir,
383 const SkString& outputDir,
384 const StringArray& matchSubstrings,
385 const StringArray& nomatchSubstrings,
386 bool recurseIntoSubdirs,
389 DiffSummary* summary) {
390 SkASSERT(!baseDir.isEmpty());
391 SkASSERT(!comparisonDir.isEmpty());
394 FileArray comparisonFiles;
396 get_file_list(baseDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, &baseFiles);
397 get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
400 if (!baseFiles.isEmpty()) {
401 qsort(baseFiles.begin(), baseFiles.count(), sizeof(SkString*),
402 SkCastForQSort(compare_file_name_metrics));
404 if (!comparisonFiles.isEmpty()) {
405 qsort(comparisonFiles.begin(), comparisonFiles.count(),
406 sizeof(SkString*), SkCastForQSort(compare_file_name_metrics));
412 while (i < baseFiles.count() &&
413 j < comparisonFiles.count()) {
415 SkString basePath(baseDir);
416 SkString comparisonPath(comparisonDir);
418 DiffRecord *drp = new DiffRecord;
419 int v = strcmp(baseFiles[i]->c_str(), comparisonFiles[j]->c_str());
422 // in baseDir, but not in comparisonDir
423 drp->fResult = DiffRecord::kCouldNotCompare_Result;
425 basePath.append(*baseFiles[i]);
426 comparisonPath.append(*baseFiles[i]);
428 drp->fBase.fFilename = *baseFiles[i];
429 drp->fBase.fFullPath = basePath;
430 drp->fBase.fStatus = DiffResource::kExists_Status;
432 drp->fComparison.fFilename = *baseFiles[i];
433 drp->fComparison.fFullPath = comparisonPath;
434 drp->fComparison.fStatus = DiffResource::kDoesNotExist_Status;
436 VERBOSE_STATUS("MISSING", ANSI_COLOR_YELLOW, baseFiles[i]);
440 // in comparisonDir, but not in baseDir
441 drp->fResult = DiffRecord::kCouldNotCompare_Result;
443 basePath.append(*comparisonFiles[j]);
444 comparisonPath.append(*comparisonFiles[j]);
446 drp->fBase.fFilename = *comparisonFiles[j];
447 drp->fBase.fFullPath = basePath;
448 drp->fBase.fStatus = DiffResource::kDoesNotExist_Status;
450 drp->fComparison.fFilename = *comparisonFiles[j];
451 drp->fComparison.fFullPath = comparisonPath;
452 drp->fComparison.fStatus = DiffResource::kExists_Status;
454 VERBOSE_STATUS("MISSING", ANSI_COLOR_YELLOW, comparisonFiles[j]);
458 // Found the same filename in both baseDir and comparisonDir.
459 SkASSERT(DiffRecord::kUnknown_Result == drp->fResult);
461 basePath.append(*baseFiles[i]);
462 comparisonPath.append(*comparisonFiles[j]);
464 drp->fBase.fFilename = *baseFiles[i];
465 drp->fBase.fFullPath = basePath;
466 drp->fBase.fStatus = DiffResource::kExists_Status;
468 drp->fComparison.fFilename = *comparisonFiles[j];
469 drp->fComparison.fFullPath = comparisonPath;
470 drp->fComparison.fStatus = DiffResource::kExists_Status;
472 SkAutoDataUnref baseFileBits(read_file(drp->fBase.fFullPath.c_str()));
474 drp->fBase.fStatus = DiffResource::kRead_Status;
476 SkAutoDataUnref comparisonFileBits(read_file(drp->fComparison.fFullPath.c_str()));
477 if (comparisonFileBits) {
478 drp->fComparison.fStatus = DiffResource::kRead_Status;
480 if (nullptr == baseFileBits || nullptr == comparisonFileBits) {
481 if (nullptr == baseFileBits) {
482 drp->fBase.fStatus = DiffResource::kCouldNotRead_Status;
483 VERBOSE_STATUS("READ FAIL", ANSI_COLOR_RED, baseFiles[i]);
485 if (nullptr == comparisonFileBits) {
486 drp->fComparison.fStatus = DiffResource::kCouldNotRead_Status;
487 VERBOSE_STATUS("READ FAIL", ANSI_COLOR_RED, comparisonFiles[j]);
489 drp->fResult = DiffRecord::kCouldNotCompare_Result;
491 } else if (are_buffers_equal(baseFileBits, comparisonFileBits)) {
492 drp->fResult = DiffRecord::kEqualBits_Result;
493 VERBOSE_STATUS("MATCH", ANSI_COLOR_GREEN, baseFiles[i]);
495 AutoReleasePixels arp(drp);
496 get_bitmap(baseFileBits, drp->fBase, SkImageDecoder::kDecodePixels_Mode);
497 get_bitmap(comparisonFileBits, drp->fComparison,
498 SkImageDecoder::kDecodePixels_Mode);
499 VERBOSE_STATUS("DIFFERENT", ANSI_COLOR_RED, baseFiles[i]);
500 if (DiffResource::kDecoded_Status == drp->fBase.fStatus &&
501 DiffResource::kDecoded_Status == drp->fComparison.fStatus) {
502 create_and_write_diff_image(drp, dmp, colorThreshold,
503 outputDir, drp->fBase.fFilename);
505 drp->fResult = DiffRecord::kCouldNotCompare_Result;
516 SkASSERT(DiffRecord::kUnknown_Result != drp->fResult);
517 differences->push(drp);
521 for (; i < baseFiles.count(); ++i) {
522 // files only in baseDir
523 DiffRecord *drp = new DiffRecord();
524 drp->fBase.fFilename = *baseFiles[i];
525 drp->fBase.fFullPath = baseDir;
526 drp->fBase.fFullPath.append(drp->fBase.fFilename);
527 drp->fBase.fStatus = DiffResource::kExists_Status;
529 drp->fComparison.fFilename = *baseFiles[i];
530 drp->fComparison.fFullPath = comparisonDir;
531 drp->fComparison.fFullPath.append(drp->fComparison.fFilename);
532 drp->fComparison.fStatus = DiffResource::kDoesNotExist_Status;
534 drp->fResult = DiffRecord::kCouldNotCompare_Result;
538 differences->push(drp);
542 for (; j < comparisonFiles.count(); ++j) {
543 // files only in comparisonDir
544 DiffRecord *drp = new DiffRecord();
545 drp->fBase.fFilename = *comparisonFiles[j];
546 drp->fBase.fFullPath = baseDir;
547 drp->fBase.fFullPath.append(drp->fBase.fFilename);
548 drp->fBase.fStatus = DiffResource::kDoesNotExist_Status;
550 drp->fComparison.fFilename = *comparisonFiles[j];
551 drp->fComparison.fFullPath = comparisonDir;
552 drp->fComparison.fFullPath.append(drp->fComparison.fFilename);
553 drp->fComparison.fStatus = DiffResource::kExists_Status;
555 drp->fResult = DiffRecord::kCouldNotCompare_Result;
559 differences->push(drp);
563 release_file_list(&baseFiles);
564 release_file_list(&comparisonFiles);
567 static void usage (char * argv0) {
568 SkDebugf("Skia baseline image diff tool\n");
571 " %s <baseDir> <comparisonDir> [outputDir] \n", argv0);
574 "\n --failonresult <result>: After comparing all file pairs, exit with nonzero"
575 "\n return code (number of file pairs yielding this"
576 "\n result) if any file pairs yielded this result."
577 "\n This flag may be repeated, in which case the"
578 "\n return code will be the number of fail pairs"
579 "\n yielding ANY of these results."
580 "\n --failonstatus <baseStatus> <comparisonStatus>: exit with nonzero return"
581 "\n code if any file pairs yielded this status."
582 "\n --help: display this info"
583 "\n --listfilenames: list all filenames for each result type in stdout"
584 "\n --match <substring>: compare files whose filenames contain this substring;"
585 "\n if unspecified, compare ALL files."
586 "\n this flag may be repeated."
587 "\n --nodiffs: don't write out image diffs or index.html, just generate"
588 "\n report on stdout"
589 "\n --nomatch <substring>: regardless of --match, DO NOT compare files whose"
590 "\n filenames contain this substring."
591 "\n this flag may be repeated."
592 "\n --noprintdirs: do not print the directories used."
593 "\n --norecurse: do not recurse into subdirectories."
594 "\n --sortbymaxmismatch: sort by worst color channel mismatch;"
595 "\n break ties with -sortbymismatch"
596 "\n --sortbymismatch: sort by average color channel mismatch"
597 "\n --threshold <n>: only report differences > n (per color channel) [default 0]"
598 "\n --weighted: sort by # pixels different weighted by color difference"
600 "\n baseDir: directory to read baseline images from."
601 "\n comparisonDir: directory to read comparison images from"
602 "\n outputDir: directory to write difference images and index.html to;"
603 "\n defaults to comparisonDir"
605 "\nIf no sort is specified, it will sort by fraction of pixels mismatching."
609 const int kNoError = 0;
610 const int kGenericError = -1;
612 int tool_main(int argc, char** argv);
613 int tool_main(int argc, char** argv) {
614 DiffMetricProc diffProc = compute_diff_pmcolor;
615 int (*sortProc)(const void*, const void*) = compare<CompareDiffMetrics>;
617 // Maximum error tolerated in any one color channel in any one pixel before
618 // a difference is reported.
619 int colorThreshold = 0;
621 SkString comparisonDir;
624 StringArray matchSubstrings;
625 StringArray nomatchSubstrings;
627 bool generateDiffs = true;
628 bool listFilenames = false;
629 bool printDirNames = true;
630 bool recurseIntoSubdirs = true;
631 bool verbose = false;
632 bool listFailingBase = false;
634 RecordArray differences;
637 bool failOnResultType[DiffRecord::kResultCount];
638 for (int i = 0; i < DiffRecord::kResultCount; i++) {
639 failOnResultType[i] = false;
642 bool failOnStatusType[DiffResource::kStatusCount][DiffResource::kStatusCount];
643 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
644 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
645 failOnStatusType[base][comparison] = false;
650 int numUnflaggedArguments = 0;
651 for (i = 1; i < argc; i++) {
652 if (!strcmp(argv[i], "--failonresult")) {
654 SkDebugf("failonresult expects one argument.\n");
657 DiffRecord::Result type = DiffRecord::getResultByName(argv[i]);
658 if (type != DiffRecord::kResultCount) {
659 failOnResultType[type] = true;
661 SkDebugf("ignoring unrecognized result <%s>\n", argv[i]);
665 if (!strcmp(argv[i], "--failonstatus")) {
667 SkDebugf("failonstatus missing base status.\n");
670 bool baseStatuses[DiffResource::kStatusCount];
671 if (!DiffResource::getMatchingStatuses(argv[i], baseStatuses)) {
672 SkDebugf("unrecognized base status <%s>\n", argv[i]);
676 SkDebugf("failonstatus missing comparison status.\n");
679 bool comparisonStatuses[DiffResource::kStatusCount];
680 if (!DiffResource::getMatchingStatuses(argv[i], comparisonStatuses)) {
681 SkDebugf("unrecognized comarison status <%s>\n", argv[i]);
684 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
685 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
686 failOnStatusType[base][comparison] |=
687 baseStatuses[base] && comparisonStatuses[comparison];
692 if (!strcmp(argv[i], "--help")) {
696 if (!strcmp(argv[i], "--listfilenames")) {
697 listFilenames = true;
700 if (!strcmp(argv[i], "--verbose")) {
704 if (!strcmp(argv[i], "--match")) {
705 matchSubstrings.push(new SkString(argv[++i]));
708 if (!strcmp(argv[i], "--nodiffs")) {
709 generateDiffs = false;
712 if (!strcmp(argv[i], "--nomatch")) {
713 nomatchSubstrings.push(new SkString(argv[++i]));
716 if (!strcmp(argv[i], "--noprintdirs")) {
717 printDirNames = false;
720 if (!strcmp(argv[i], "--norecurse")) {
721 recurseIntoSubdirs = false;
724 if (!strcmp(argv[i], "--sortbymaxmismatch")) {
725 sortProc = compare<CompareDiffMaxMismatches>;
728 if (!strcmp(argv[i], "--sortbymismatch")) {
729 sortProc = compare<CompareDiffMeanMismatches>;
732 if (!strcmp(argv[i], "--threshold")) {
733 colorThreshold = atoi(argv[++i]);
736 if (!strcmp(argv[i], "--weighted")) {
737 sortProc = compare<CompareDiffWeighted>;
740 if (argv[i][0] != '-') {
741 switch (numUnflaggedArguments++) {
743 baseDir.set(argv[i]);
746 comparisonDir.set(argv[i]);
749 outputDir.set(argv[i]);
752 SkDebugf("extra unflagged argument <%s>\n", argv[i]);
754 return kGenericError;
757 if (!strcmp(argv[i], "--listFailingBase")) {
758 listFailingBase = true;
762 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
764 return kGenericError;
767 if (numUnflaggedArguments == 2) {
768 outputDir = comparisonDir;
769 } else if (numUnflaggedArguments != 3) {
771 return kGenericError;
774 if (!baseDir.endsWith(PATH_DIV_STR)) {
775 baseDir.append(PATH_DIV_STR);
778 printf("baseDir is [%s]\n", baseDir.c_str());
781 if (!comparisonDir.endsWith(PATH_DIV_STR)) {
782 comparisonDir.append(PATH_DIV_STR);
785 printf("comparisonDir is [%s]\n", comparisonDir.c_str());
788 if (!outputDir.endsWith(PATH_DIV_STR)) {
789 outputDir.append(PATH_DIV_STR);
793 printf("writing diffs to outputDir is [%s]\n", outputDir.c_str());
797 printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str());
802 // If no matchSubstrings were specified, match ALL strings
803 // (except for whatever nomatchSubstrings were specified, if any).
804 if (matchSubstrings.isEmpty()) {
805 matchSubstrings.push(new SkString(""));
808 create_diff_images(diffProc, colorThreshold, &differences,
809 baseDir, comparisonDir, outputDir,
810 matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, generateDiffs,
812 summary.print(listFilenames, failOnResultType, failOnStatusType);
814 if (listFailingBase) {
815 summary.printfFailingBaseNames("\n");
818 if (differences.count()) {
819 qsort(differences.begin(), differences.count(),
820 sizeof(DiffRecord*), sortProc);
824 print_diff_page(summary.fNumMatches, colorThreshold, differences,
825 baseDir, comparisonDir, outputDir);
828 for (i = 0; i < differences.count(); i++) {
829 delete differences[i];
831 matchSubstrings.deleteAll();
832 nomatchSubstrings.deleteAll();
834 int num_failing_results = 0;
835 for (int i = 0; i < DiffRecord::kResultCount; i++) {
836 if (failOnResultType[i]) {
837 num_failing_results += summary.fResultsOfType[i].count();
840 if (!failOnResultType[DiffRecord::kCouldNotCompare_Result]) {
841 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
842 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
843 if (failOnStatusType[base][comparison]) {
844 num_failing_results += summary.fStatusOfType[base][comparison].count();
850 // On Linux (and maybe other platforms too), any results outside of the
851 // range [0...255] are wrapped (mod 256). Do the conversion ourselves, to
852 // make sure that we only return 0 when there were no failures.
853 return (num_failing_results > 255) ? 255 : num_failing_results;
856 #if !defined SK_BUILD_FOR_IOS
857 int main(int argc, char * const argv[]) {
858 return tool_main(argc, (char**) argv);