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