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