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"
20 __SK_FORCE_IMAGE_DECODER_LINKING;
25 * Given three directory names, expects to find identically-named files in
26 * each of the first two; the first are treated as a set of baseline,
27 * the second a set of variant images, and a diff image is written into the
28 * third directory for each pair.
29 * Creates an index.html in the current third directory to compare each
30 * pair that does not match exactly.
31 * Recursively descends directories, unless run with --norecurse.
33 * Returns zero exit code if all images match across baseDir and comparisonDir.
36 typedef SkTDArray<SkString*> StringArray;
37 typedef StringArray FileArray;
39 static void add_unique_basename(StringArray* array, const SkString& filename) {
41 const char* src = filename.c_str();
42 const char* trimmed = strrchr(src, SkPATH_SEPARATOR);
44 trimmed += 1; // skip the separator
48 const char* end = strrchr(trimmed, '.');
50 end = trimmed + strlen(trimmed);
52 SkString result(trimmed, end - trimmed);
54 // only add unique entries
55 for (int i = 0; i < array->count(); ++i) {
56 if (*array->getAt(i) == result) {
60 *array->append() = new SkString(result);
68 , fMaxMismatchPercent(0) { };
71 for (int i = 0; i < DiffRecord::kResultCount; ++i) {
72 fResultsOfType[i].deleteAll();
74 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
75 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
76 fStatusOfType[base][comparison].deleteAll();
82 uint32_t fNumMismatches;
83 uint32_t fMaxMismatchV;
84 float fMaxMismatchPercent;
86 FileArray fResultsOfType[DiffRecord::kResultCount];
87 FileArray fStatusOfType[DiffResource::kStatusCount][DiffResource::kStatusCount];
89 StringArray fFailedBaseNames[DiffRecord::kResultCount];
91 void printContents(const FileArray& fileArray,
92 const char* baseStatus, const char* comparisonStatus,
94 int n = fileArray.count();
95 printf("%d file pairs %s in baseDir and %s in comparisonDir",
96 n, baseStatus, comparisonStatus);
99 for (int i = 0; i < n; ++i) {
100 printf("%s ", fileArray[i]->c_str());
106 void printStatus(bool listFilenames,
107 bool failOnStatusType[DiffResource::kStatusCount]
108 [DiffResource::kStatusCount]) {
109 typedef DiffResource::Status Status;
111 for (int base = 0; base < DiffResource::kStatusCount; ++base) {
112 Status baseStatus = static_cast<Status>(base);
113 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
114 Status comparisonStatus = static_cast<Status>(comparison);
115 const FileArray& fileArray = fStatusOfType[base][comparison];
116 if (fileArray.count() > 0) {
117 if (failOnStatusType[base][comparison]) {
122 printContents(fileArray,
123 DiffResource::getStatusDescription(baseStatus),
124 DiffResource::getStatusDescription(comparisonStatus),
131 // Print a line about the contents of this FileArray to stdout.
132 void printContents(const FileArray& fileArray, const char* headerText, bool listFilenames) {
133 int n = fileArray.count();
134 printf("%d file pairs %s", n, headerText);
137 for (int i = 0; i < n; ++i) {
138 printf("%s ", fileArray[i]->c_str());
144 void print(bool listFilenames, bool failOnResultType[DiffRecord::kResultCount],
145 bool failOnStatusType[DiffResource::kStatusCount]
146 [DiffResource::kStatusCount]) {
147 printf("\ncompared %d file pairs:\n", fNumMatches + fNumMismatches);
148 for (int resultInt = 0; resultInt < DiffRecord::kResultCount; ++resultInt) {
149 DiffRecord::Result result = static_cast<DiffRecord::Result>(resultInt);
150 if (failOnResultType[result]) {
155 printContents(fResultsOfType[result], DiffRecord::getResultDescription(result),
157 if (DiffRecord::kCouldNotCompare_Result == result) {
158 printStatus(listFilenames, failOnStatusType);
161 printf("(results marked with [*] will cause nonzero return value)\n");
162 printf("\nnumber of mismatching file pairs: %d\n", fNumMismatches);
163 if (fNumMismatches > 0) {
164 printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
165 printf("Largest area mismatch was %.2f%% of pixels\n",fMaxMismatchPercent);
169 void printfFailingBaseNames(const char separator[]) {
170 for (int resultInt = 0; resultInt < DiffRecord::kResultCount; ++resultInt) {
171 const StringArray& array = fFailedBaseNames[resultInt];
173 printf("%s [%d]%s", DiffRecord::ResultNames[resultInt], array.count(), separator);
174 for (int j = 0; j < array.count(); ++j) {
175 printf("%s%s", array[j]->c_str(), separator);
182 void add (DiffRecord* drp) {
183 uint32_t mismatchValue;
185 if (drp->fBase.fFilename.equals(drp->fComparison.fFilename)) {
186 fResultsOfType[drp->fResult].push(new SkString(drp->fBase.fFilename));
188 SkString* blame = new SkString("(");
189 blame->append(drp->fBase.fFilename);
191 blame->append(drp->fComparison.fFilename);
193 fResultsOfType[drp->fResult].push(blame);
195 switch (drp->fResult) {
196 case DiffRecord::kEqualBits_Result:
199 case DiffRecord::kEqualPixels_Result:
202 case DiffRecord::kDifferentSizes_Result:
205 case DiffRecord::kDifferentPixels_Result:
207 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
208 fMaxMismatchPercent = drp->fFractionDifference * 100;
210 mismatchValue = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
212 if (mismatchValue > fMaxMismatchV) {
213 fMaxMismatchV = mismatchValue;
216 case DiffRecord::kCouldNotCompare_Result:
218 fStatusOfType[drp->fBase.fStatus][drp->fComparison.fStatus].push(
219 new SkString(drp->fBase.fFilename));
221 case DiffRecord::kUnknown_Result:
222 SkDEBUGFAIL("adding uncategorized DiffRecord");
225 SkDEBUGFAIL("adding DiffRecord with unhandled fResult value");
229 switch (drp->fResult) {
230 case DiffRecord::kEqualBits_Result:
231 case DiffRecord::kEqualPixels_Result:
234 add_unique_basename(&fFailedBaseNames[drp->fResult], drp->fBase.fFilename);
240 /// Returns true if string contains any of these substrings.
241 static bool string_contains_any_of(const SkString& string,
242 const StringArray& substrings) {
243 for (int i = 0; i < substrings.count(); i++) {
244 if (string.contains(substrings[i]->c_str())) {
251 /// Internal (potentially recursive) implementation of get_file_list.
252 static void get_file_list_subdir(const SkString& rootDir, const SkString& subDir,
253 const StringArray& matchSubstrings,
254 const StringArray& nomatchSubstrings,
255 bool recurseIntoSubdirs, FileArray *files) {
256 bool isSubDirEmpty = subDir.isEmpty();
257 SkString dir(rootDir);
258 if (!isSubDirEmpty) {
259 dir.append(PATH_DIV_STR);
263 // Iterate over files (not directories) within dir.
264 SkOSFile::Iter fileIterator(dir.c_str());
266 while (fileIterator.next(&fileName, false)) {
267 if (fileName.startsWith(".")) {
270 SkString pathRelativeToRootDir(subDir);
271 if (!isSubDirEmpty) {
272 pathRelativeToRootDir.append(PATH_DIV_STR);
274 pathRelativeToRootDir.append(fileName);
275 if (string_contains_any_of(pathRelativeToRootDir, matchSubstrings) &&
276 !string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) {
277 files->push(new SkString(pathRelativeToRootDir));
281 // Recurse into any non-ignored subdirectories.
282 if (recurseIntoSubdirs) {
283 SkOSFile::Iter dirIterator(dir.c_str());
285 while (dirIterator.next(&dirName, true)) {
286 if (dirName.startsWith(".")) {
289 SkString pathRelativeToRootDir(subDir);
290 if (!isSubDirEmpty) {
291 pathRelativeToRootDir.append(PATH_DIV_STR);
293 pathRelativeToRootDir.append(dirName);
294 if (!string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) {
295 get_file_list_subdir(rootDir, pathRelativeToRootDir,
296 matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
303 /// Iterate over dir and get all files whose filename:
304 /// - matches any of the substrings in matchSubstrings, but...
305 /// - DOES NOT match any of the substrings in nomatchSubstrings
306 /// - DOES NOT start with a dot (.)
307 /// Adds the matching files to the list in *files.
308 static void get_file_list(const SkString& dir,
309 const StringArray& matchSubstrings,
310 const StringArray& nomatchSubstrings,
311 bool recurseIntoSubdirs, FileArray *files) {
312 get_file_list_subdir(dir, SkString(""),
313 matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
317 static void release_file_list(FileArray *files) {
321 /// Comparison routines for qsort, sort by file names.
322 static int compare_file_name_metrics(SkString **lhs, SkString **rhs) {
323 return strcmp((*lhs)->c_str(), (*rhs)->c_str());
326 class AutoReleasePixels {
328 AutoReleasePixels(DiffRecord* drp)
330 SkASSERT(drp != NULL);
332 ~AutoReleasePixels() {
333 fDrp->fBase.fBitmap.setPixelRef(NULL);
334 fDrp->fComparison.fBitmap.setPixelRef(NULL);
335 fDrp->fDifference.fBitmap.setPixelRef(NULL);
336 fDrp->fWhite.fBitmap.setPixelRef(NULL);
343 static void get_bounds(DiffResource& resource, const char* name) {
344 if (resource.fBitmap.empty() && !DiffResource::isStatusFailed(resource.fStatus)) {
345 SkAutoDataUnref fileBits(read_file(resource.fFullPath.c_str()));
346 if (NULL == fileBits) {
347 SkDebugf("WARNING: couldn't read %s file <%s>\n", name, resource.fFullPath.c_str());
348 resource.fStatus = DiffResource::kCouldNotRead_Status;
350 get_bitmap(fileBits, resource, SkImageDecoder::kDecodeBounds_Mode);
355 static void get_bounds(DiffRecord& drp) {
356 get_bounds(drp.fBase, "base");
357 get_bounds(drp.fComparison, "comparison");
361 #define ANSI_COLOR_RED ""
362 #define ANSI_COLOR_GREEN ""
363 #define ANSI_COLOR_YELLOW ""
364 #define ANSI_COLOR_RESET ""
366 #define ANSI_COLOR_RED "\x1b[31m"
367 #define ANSI_COLOR_GREEN "\x1b[32m"
368 #define ANSI_COLOR_YELLOW "\x1b[33m"
369 #define ANSI_COLOR_RESET "\x1b[0m"
372 #define VERBOSE_STATUS(status,color,filename) if (verbose) printf( "[ " color " %10s " ANSI_COLOR_RESET " ] %s\n", status, filename->c_str())
374 /// Creates difference images, returns the number that have a 0 metric.
375 /// If outputDir.isEmpty(), don't write out diff files.
376 static void create_diff_images (DiffMetricProc dmp,
377 const int colorThreshold,
378 RecordArray* differences,
379 const SkString& baseDir,
380 const SkString& comparisonDir,
381 const SkString& outputDir,
382 const StringArray& matchSubstrings,
383 const StringArray& nomatchSubstrings,
384 bool recurseIntoSubdirs,
387 DiffSummary* summary) {
388 SkASSERT(!baseDir.isEmpty());
389 SkASSERT(!comparisonDir.isEmpty());
392 FileArray comparisonFiles;
394 get_file_list(baseDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, &baseFiles);
395 get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
398 if (!baseFiles.isEmpty()) {
399 qsort(baseFiles.begin(), baseFiles.count(), sizeof(SkString*),
400 SkCastForQSort(compare_file_name_metrics));
402 if (!comparisonFiles.isEmpty()) {
403 qsort(comparisonFiles.begin(), comparisonFiles.count(),
404 sizeof(SkString*), SkCastForQSort(compare_file_name_metrics));
410 while (i < baseFiles.count() &&
411 j < comparisonFiles.count()) {
413 SkString basePath(baseDir);
414 SkString comparisonPath(comparisonDir);
416 DiffRecord *drp = new DiffRecord;
417 int v = strcmp(baseFiles[i]->c_str(), comparisonFiles[j]->c_str());
420 // in baseDir, but not in comparisonDir
421 drp->fResult = DiffRecord::kCouldNotCompare_Result;
423 basePath.append(*baseFiles[i]);
424 comparisonPath.append(*baseFiles[i]);
426 drp->fBase.fFilename = *baseFiles[i];
427 drp->fBase.fFullPath = basePath;
428 drp->fBase.fStatus = DiffResource::kExists_Status;
430 drp->fComparison.fFilename = *baseFiles[i];
431 drp->fComparison.fFullPath = comparisonPath;
432 drp->fComparison.fStatus = DiffResource::kDoesNotExist_Status;
434 VERBOSE_STATUS("MISSING", ANSI_COLOR_YELLOW, baseFiles[i]);
438 // in comparisonDir, but not in baseDir
439 drp->fResult = DiffRecord::kCouldNotCompare_Result;
441 basePath.append(*comparisonFiles[j]);
442 comparisonPath.append(*comparisonFiles[j]);
444 drp->fBase.fFilename = *comparisonFiles[j];
445 drp->fBase.fFullPath = basePath;
446 drp->fBase.fStatus = DiffResource::kDoesNotExist_Status;
448 drp->fComparison.fFilename = *comparisonFiles[j];
449 drp->fComparison.fFullPath = comparisonPath;
450 drp->fComparison.fStatus = DiffResource::kExists_Status;
452 VERBOSE_STATUS("MISSING", ANSI_COLOR_YELLOW, comparisonFiles[j]);
456 // Found the same filename in both baseDir and comparisonDir.
457 SkASSERT(DiffRecord::kUnknown_Result == drp->fResult);
459 basePath.append(*baseFiles[i]);
460 comparisonPath.append(*comparisonFiles[j]);
462 drp->fBase.fFilename = *baseFiles[i];
463 drp->fBase.fFullPath = basePath;
464 drp->fBase.fStatus = DiffResource::kExists_Status;
466 drp->fComparison.fFilename = *comparisonFiles[j];
467 drp->fComparison.fFullPath = comparisonPath;
468 drp->fComparison.fStatus = DiffResource::kExists_Status;
470 SkAutoDataUnref baseFileBits(read_file(drp->fBase.fFullPath.c_str()));
472 drp->fBase.fStatus = DiffResource::kRead_Status;
474 SkAutoDataUnref comparisonFileBits(read_file(drp->fComparison.fFullPath.c_str()));
475 if (comparisonFileBits) {
476 drp->fComparison.fStatus = DiffResource::kRead_Status;
478 if (NULL == baseFileBits || NULL == comparisonFileBits) {
479 if (NULL == baseFileBits) {
480 drp->fBase.fStatus = DiffResource::kCouldNotRead_Status;
481 VERBOSE_STATUS("READ FAIL", ANSI_COLOR_RED, baseFiles[i]);
483 if (NULL == comparisonFileBits) {
484 drp->fComparison.fStatus = DiffResource::kCouldNotRead_Status;
485 VERBOSE_STATUS("READ FAIL", ANSI_COLOR_RED, comparisonFiles[j]);
487 drp->fResult = DiffRecord::kCouldNotCompare_Result;
489 } else if (are_buffers_equal(baseFileBits, comparisonFileBits)) {
490 drp->fResult = DiffRecord::kEqualBits_Result;
491 VERBOSE_STATUS("MATCH", ANSI_COLOR_GREEN, baseFiles[i]);
493 AutoReleasePixels arp(drp);
494 get_bitmap(baseFileBits, drp->fBase, SkImageDecoder::kDecodePixels_Mode);
495 get_bitmap(comparisonFileBits, drp->fComparison,
496 SkImageDecoder::kDecodePixels_Mode);
497 VERBOSE_STATUS("DIFFERENT", ANSI_COLOR_RED, baseFiles[i]);
498 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);
503 drp->fResult = DiffRecord::kCouldNotCompare_Result;
514 SkASSERT(DiffRecord::kUnknown_Result != drp->fResult);
515 differences->push(drp);
519 for (; i < baseFiles.count(); ++i) {
520 // files only in baseDir
521 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;
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;
532 drp->fResult = DiffRecord::kCouldNotCompare_Result;
536 differences->push(drp);
540 for (; j < comparisonFiles.count(); ++j) {
541 // files only in comparisonDir
542 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;
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;
553 drp->fResult = DiffRecord::kCouldNotCompare_Result;
557 differences->push(drp);
561 release_file_list(&baseFiles);
562 release_file_list(&comparisonFiles);
565 static void usage (char * argv0) {
566 SkDebugf("Skia baseline image diff tool\n");
569 " %s <baseDir> <comparisonDir> [outputDir] \n", argv0);
572 "\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."
578 "\n --failonstatus <baseStatus> <comparisonStatus>: exit with nonzero return"
579 "\n code if any file pairs yielded this status."
580 "\n --help: display this info"
581 "\n --listfilenames: list all filenames for each result type in stdout"
582 "\n --match <substring>: compare files whose filenames contain this substring;"
583 "\n if unspecified, compare ALL files."
584 "\n this flag may be repeated."
585 "\n --nodiffs: don't write out image diffs or index.html, just generate"
586 "\n report on stdout"
587 "\n --nomatch <substring>: regardless of --match, DO NOT compare files whose"
588 "\n filenames contain this substring."
589 "\n this flag may be repeated."
590 "\n --noprintdirs: do not print the directories used."
591 "\n --norecurse: do not recurse into subdirectories."
592 "\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"
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"
603 "\nIf no sort is specified, it will sort by fraction of pixels mismatching."
607 const int kNoError = 0;
608 const int kGenericError = -1;
610 int tool_main(int argc, char** argv);
611 int tool_main(int argc, char** argv) {
612 DiffMetricProc diffProc = compute_diff_pmcolor;
613 int (*sortProc)(const void*, const void*) = compare<CompareDiffMetrics>;
615 // Maximum error tolerated in any one color channel in any one pixel before
616 // a difference is reported.
617 int colorThreshold = 0;
619 SkString comparisonDir;
622 StringArray matchSubstrings;
623 StringArray nomatchSubstrings;
625 bool generateDiffs = true;
626 bool listFilenames = false;
627 bool printDirNames = true;
628 bool recurseIntoSubdirs = true;
629 bool verbose = false;
630 bool listFailingBase = false;
632 RecordArray differences;
635 bool failOnResultType[DiffRecord::kResultCount];
636 for (int i = 0; i < DiffRecord::kResultCount; i++) {
637 failOnResultType[i] = false;
640 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;
648 int numUnflaggedArguments = 0;
649 for (i = 1; i < argc; i++) {
650 if (!strcmp(argv[i], "--failonresult")) {
652 SkDebugf("failonresult expects one argument.\n");
655 DiffRecord::Result type = DiffRecord::getResultByName(argv[i]);
656 if (type != DiffRecord::kResultCount) {
657 failOnResultType[type] = true;
659 SkDebugf("ignoring unrecognized result <%s>\n", argv[i]);
663 if (!strcmp(argv[i], "--failonstatus")) {
665 SkDebugf("failonstatus missing base status.\n");
668 bool baseStatuses[DiffResource::kStatusCount];
669 if (!DiffResource::getMatchingStatuses(argv[i], baseStatuses)) {
670 SkDebugf("unrecognized base status <%s>\n", argv[i]);
674 SkDebugf("failonstatus missing comparison status.\n");
677 bool comparisonStatuses[DiffResource::kStatusCount];
678 if (!DiffResource::getMatchingStatuses(argv[i], comparisonStatuses)) {
679 SkDebugf("unrecognized comarison status <%s>\n", argv[i]);
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];
690 if (!strcmp(argv[i], "--help")) {
694 if (!strcmp(argv[i], "--listfilenames")) {
695 listFilenames = true;
698 if (!strcmp(argv[i], "--verbose")) {
702 if (!strcmp(argv[i], "--match")) {
703 matchSubstrings.push(new SkString(argv[++i]));
706 if (!strcmp(argv[i], "--nodiffs")) {
707 generateDiffs = false;
710 if (!strcmp(argv[i], "--nomatch")) {
711 nomatchSubstrings.push(new SkString(argv[++i]));
714 if (!strcmp(argv[i], "--noprintdirs")) {
715 printDirNames = false;
718 if (!strcmp(argv[i], "--norecurse")) {
719 recurseIntoSubdirs = false;
722 if (!strcmp(argv[i], "--sortbymaxmismatch")) {
723 sortProc = compare<CompareDiffMaxMismatches>;
726 if (!strcmp(argv[i], "--sortbymismatch")) {
727 sortProc = compare<CompareDiffMeanMismatches>;
730 if (!strcmp(argv[i], "--threshold")) {
731 colorThreshold = atoi(argv[++i]);
734 if (!strcmp(argv[i], "--weighted")) {
735 sortProc = compare<CompareDiffWeighted>;
738 if (argv[i][0] != '-') {
739 switch (numUnflaggedArguments++) {
741 baseDir.set(argv[i]);
744 comparisonDir.set(argv[i]);
747 outputDir.set(argv[i]);
750 SkDebugf("extra unflagged argument <%s>\n", argv[i]);
752 return kGenericError;
755 if (!strcmp(argv[i], "--listFailingBase")) {
756 listFailingBase = true;
760 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
762 return kGenericError;
765 if (numUnflaggedArguments == 2) {
766 outputDir = comparisonDir;
767 } else if (numUnflaggedArguments != 3) {
769 return kGenericError;
772 if (!baseDir.endsWith(PATH_DIV_STR)) {
773 baseDir.append(PATH_DIV_STR);
776 printf("baseDir is [%s]\n", baseDir.c_str());
779 if (!comparisonDir.endsWith(PATH_DIV_STR)) {
780 comparisonDir.append(PATH_DIV_STR);
783 printf("comparisonDir is [%s]\n", comparisonDir.c_str());
786 if (!outputDir.endsWith(PATH_DIV_STR)) {
787 outputDir.append(PATH_DIV_STR);
791 printf("writing diffs to outputDir is [%s]\n", outputDir.c_str());
795 printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str());
800 // If no matchSubstrings were specified, match ALL strings
801 // (except for whatever nomatchSubstrings were specified, if any).
802 if (matchSubstrings.isEmpty()) {
803 matchSubstrings.push(new SkString(""));
806 create_diff_images(diffProc, colorThreshold, &differences,
807 baseDir, comparisonDir, outputDir,
808 matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, generateDiffs,
810 summary.print(listFilenames, failOnResultType, failOnStatusType);
812 if (listFailingBase) {
813 summary.printfFailingBaseNames("\n");
816 if (differences.count()) {
817 qsort(differences.begin(), differences.count(),
818 sizeof(DiffRecord*), sortProc);
822 print_diff_page(summary.fNumMatches, colorThreshold, differences,
823 baseDir, comparisonDir, outputDir);
826 for (i = 0; i < differences.count(); i++) {
827 delete differences[i];
829 matchSubstrings.deleteAll();
830 nomatchSubstrings.deleteAll();
832 int num_failing_results = 0;
833 for (int i = 0; i < DiffRecord::kResultCount; i++) {
834 if (failOnResultType[i]) {
835 num_failing_results += summary.fResultsOfType[i].count();
838 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();
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;
854 #if !defined SK_BUILD_FOR_IOS
855 int main(int argc, char * const argv[]) {
856 return tool_main(argc, (char**) argv);