Move SkTemplates.h to private.
[platform/upstream/libSkiaSharp.git] / tools / skdiff_main.cpp
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 "SkTSearch.h"
19
20 __SK_FORCE_IMAGE_DECODER_LINKING;
21
22 /**
23  * skdiff
24  *
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.
32  *
33  * Returns zero exit code if all images match across baseDir and comparisonDir.
34  */
35
36 typedef SkTDArray<SkString*> StringArray;
37 typedef StringArray FileArray;
38
39 static void add_unique_basename(StringArray* array, const SkString& filename) {
40     // trim off dirs
41     const char* src = filename.c_str();
42     const char* trimmed = strrchr(src, SkPATH_SEPARATOR);
43     if (trimmed) {
44         trimmed += 1;   // skip the separator
45     } else {
46         trimmed = src;
47     }
48     const char* end = strrchr(trimmed, '.');
49     if (!end) {
50         end = trimmed + strlen(trimmed);
51     }
52     SkString result(trimmed, end - trimmed);
53
54     // only add unique entries
55     for (int i = 0; i < array->count(); ++i) {
56         if (*array->getAt(i) == result) {
57             return;
58         }
59     }
60     *array->append() = new SkString(result);
61 }
62
63 struct DiffSummary {
64     DiffSummary ()
65         : fNumMatches(0)
66         , fNumMismatches(0)
67         , fMaxMismatchV(0)
68         , fMaxMismatchPercent(0) { };
69
70     ~DiffSummary() {
71         for (int i = 0; i < DiffRecord::kResultCount; ++i) {
72             fResultsOfType[i].deleteAll();
73         }
74         for (int base = 0; base < DiffResource::kStatusCount; ++base) {
75             for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) {
76                 fStatusOfType[base][comparison].deleteAll();
77             }
78         }
79     }
80
81     uint32_t fNumMatches;
82     uint32_t fNumMismatches;
83     uint32_t fMaxMismatchV;
84     float fMaxMismatchPercent;
85
86     FileArray fResultsOfType[DiffRecord::kResultCount];
87     FileArray fStatusOfType[DiffResource::kStatusCount][DiffResource::kStatusCount];
88
89     StringArray fFailedBaseNames[DiffRecord::kResultCount];
90
91     void printContents(const FileArray& fileArray,
92                        const char* baseStatus, const char* comparisonStatus,
93                        bool listFilenames) {
94         int n = fileArray.count();
95         printf("%d file pairs %s in baseDir and %s in comparisonDir",
96                 n,            baseStatus,       comparisonStatus);
97         if (listFilenames) {
98             printf(": ");
99             for (int i = 0; i < n; ++i) {
100                 printf("%s ", fileArray[i]->c_str());
101             }
102         }
103         printf("\n");
104     }
105
106     void printStatus(bool listFilenames,
107                      bool failOnStatusType[DiffResource::kStatusCount]
108                                           [DiffResource::kStatusCount]) {
109         typedef DiffResource::Status Status;
110
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]) {
118                         printf("   [*] ");
119                     } else {
120                         printf("   [_] ");
121                     }
122                     printContents(fileArray,
123                                   DiffResource::getStatusDescription(baseStatus),
124                                   DiffResource::getStatusDescription(comparisonStatus),
125                                   listFilenames);
126                 }
127             }
128         }
129     }
130
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);
135         if (listFilenames) {
136             printf(": ");
137             for (int i = 0; i < n; ++i) {
138                 printf("%s ", fileArray[i]->c_str());
139             }
140         }
141         printf("\n");
142     }
143
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]) {
151                 printf("[*] ");
152             } else {
153                 printf("[_] ");
154             }
155             printContents(fResultsOfType[result], DiffRecord::getResultDescription(result),
156                           listFilenames);
157             if (DiffRecord::kCouldNotCompare_Result == result) {
158                 printStatus(listFilenames, failOnStatusType);
159             }
160         }
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);
166         }
167     }
168
169     void printfFailingBaseNames(const char separator[]) {
170         for (int resultInt = 0; resultInt < DiffRecord::kResultCount; ++resultInt) {
171             const StringArray& array = fFailedBaseNames[resultInt];
172             if (array.count()) {
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);
176                 }
177                 printf("\n");
178             }
179         }
180     }
181
182     void add (DiffRecord* drp) {
183         uint32_t mismatchValue;
184
185         if (drp->fBase.fFilename.equals(drp->fComparison.fFilename)) {
186             fResultsOfType[drp->fResult].push(new SkString(drp->fBase.fFilename));
187         } else {
188             SkString* blame = new SkString("(");
189             blame->append(drp->fBase.fFilename);
190             blame->append(", ");
191             blame->append(drp->fComparison.fFilename);
192             blame->append(")");
193             fResultsOfType[drp->fResult].push(blame);
194         }
195         switch (drp->fResult) {
196           case DiffRecord::kEqualBits_Result:
197             fNumMatches++;
198             break;
199           case DiffRecord::kEqualPixels_Result:
200             fNumMatches++;
201             break;
202           case DiffRecord::kDifferentSizes_Result:
203             fNumMismatches++;
204             break;
205           case DiffRecord::kDifferentPixels_Result:
206             fNumMismatches++;
207             if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
208                 fMaxMismatchPercent = drp->fFractionDifference * 100;
209             }
210             mismatchValue = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
211                                  drp->fMaxMismatchB);
212             if (mismatchValue > fMaxMismatchV) {
213                 fMaxMismatchV = mismatchValue;
214             }
215             break;
216           case DiffRecord::kCouldNotCompare_Result:
217             fNumMismatches++;
218             fStatusOfType[drp->fBase.fStatus][drp->fComparison.fStatus].push(
219                     new SkString(drp->fBase.fFilename));
220             break;
221           case DiffRecord::kUnknown_Result:
222             SkDEBUGFAIL("adding uncategorized DiffRecord");
223             break;
224           default:
225             SkDEBUGFAIL("adding DiffRecord with unhandled fResult value");
226             break;
227         }
228
229         switch (drp->fResult) {
230             case DiffRecord::kEqualBits_Result:
231             case DiffRecord::kEqualPixels_Result:
232                 break;
233             default:
234                 add_unique_basename(&fFailedBaseNames[drp->fResult], drp->fBase.fFilename);
235                 break;
236         }
237     }
238 };
239
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())) {
245             return true;
246         }
247     }
248     return false;
249 }
250
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);
260         dir.append(subDir);
261     }
262
263     // Iterate over files (not directories) within dir.
264     SkOSFile::Iter fileIterator(dir.c_str());
265     SkString fileName;
266     while (fileIterator.next(&fileName, false)) {
267         if (fileName.startsWith(".")) {
268             continue;
269         }
270         SkString pathRelativeToRootDir(subDir);
271         if (!isSubDirEmpty) {
272             pathRelativeToRootDir.append(PATH_DIV_STR);
273         }
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));
278         }
279     }
280
281     // Recurse into any non-ignored subdirectories.
282     if (recurseIntoSubdirs) {
283         SkOSFile::Iter dirIterator(dir.c_str());
284         SkString dirName;
285         while (dirIterator.next(&dirName, true)) {
286             if (dirName.startsWith(".")) {
287                 continue;
288             }
289             SkString pathRelativeToRootDir(subDir);
290             if (!isSubDirEmpty) {
291                 pathRelativeToRootDir.append(PATH_DIV_STR);
292             }
293             pathRelativeToRootDir.append(dirName);
294             if (!string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) {
295                 get_file_list_subdir(rootDir, pathRelativeToRootDir,
296                                      matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
297                                      files);
298             }
299         }
300     }
301 }
302
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,
314                          files);
315 }
316
317 static void release_file_list(FileArray *files) {
318     files->deleteAll();
319 }
320
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());
324 }
325
326 class AutoReleasePixels {
327 public:
328     AutoReleasePixels(DiffRecord* drp)
329     : fDrp(drp) {
330         SkASSERT(drp != NULL);
331     }
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);
337     }
338
339 private:
340     DiffRecord* fDrp;
341 };
342
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;
349         } else {
350             get_bitmap(fileBits, resource, SkImageDecoder::kDecodeBounds_Mode);
351         }
352     }
353 }
354
355 static void get_bounds(DiffRecord& drp) {
356     get_bounds(drp.fBase, "base");
357     get_bounds(drp.fComparison, "comparison");
358 }
359
360 #ifdef SK_OS_WIN
361 #define ANSI_COLOR_RED     ""
362 #define ANSI_COLOR_GREEN   ""
363 #define ANSI_COLOR_YELLOW  ""
364 #define ANSI_COLOR_RESET   ""
365 #else
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"
370 #endif
371
372 #define VERBOSE_STATUS(status,color,filename) if (verbose) printf( "[ " color " %10s " ANSI_COLOR_RESET " ] %s\n", status, filename->c_str())
373
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,
385                                 bool getBounds,
386                                 bool verbose,
387                                 DiffSummary* summary) {
388     SkASSERT(!baseDir.isEmpty());
389     SkASSERT(!comparisonDir.isEmpty());
390
391     FileArray baseFiles;
392     FileArray comparisonFiles;
393
394     get_file_list(baseDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, &baseFiles);
395     get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
396                   &comparisonFiles);
397
398     if (!baseFiles.isEmpty()) {
399         qsort(baseFiles.begin(), baseFiles.count(), sizeof(SkString*),
400               SkCastForQSort(compare_file_name_metrics));
401     }
402     if (!comparisonFiles.isEmpty()) {
403         qsort(comparisonFiles.begin(), comparisonFiles.count(),
404               sizeof(SkString*), SkCastForQSort(compare_file_name_metrics));
405     }
406
407     int i = 0;
408     int j = 0;
409
410     while (i < baseFiles.count() &&
411            j < comparisonFiles.count()) {
412
413         SkString basePath(baseDir);
414         SkString comparisonPath(comparisonDir);
415
416         DiffRecord *drp = new DiffRecord;
417         int v = strcmp(baseFiles[i]->c_str(), comparisonFiles[j]->c_str());
418
419         if (v < 0) {
420             // in baseDir, but not in comparisonDir
421             drp->fResult = DiffRecord::kCouldNotCompare_Result;
422
423             basePath.append(*baseFiles[i]);
424             comparisonPath.append(*baseFiles[i]);
425
426             drp->fBase.fFilename = *baseFiles[i];
427             drp->fBase.fFullPath = basePath;
428             drp->fBase.fStatus = DiffResource::kExists_Status;
429
430             drp->fComparison.fFilename = *baseFiles[i];
431             drp->fComparison.fFullPath = comparisonPath;
432             drp->fComparison.fStatus = DiffResource::kDoesNotExist_Status;
433
434             VERBOSE_STATUS("MISSING", ANSI_COLOR_YELLOW, baseFiles[i]);
435
436             ++i;
437         } else if (v > 0) {
438             // in comparisonDir, but not in baseDir
439             drp->fResult = DiffRecord::kCouldNotCompare_Result;
440
441             basePath.append(*comparisonFiles[j]);
442             comparisonPath.append(*comparisonFiles[j]);
443
444             drp->fBase.fFilename = *comparisonFiles[j];
445             drp->fBase.fFullPath = basePath;
446             drp->fBase.fStatus = DiffResource::kDoesNotExist_Status;
447
448             drp->fComparison.fFilename = *comparisonFiles[j];
449             drp->fComparison.fFullPath = comparisonPath;
450             drp->fComparison.fStatus = DiffResource::kExists_Status;
451
452             VERBOSE_STATUS("MISSING", ANSI_COLOR_YELLOW, comparisonFiles[j]);
453
454             ++j;
455         } else {
456             // Found the same filename in both baseDir and comparisonDir.
457             SkASSERT(DiffRecord::kUnknown_Result == drp->fResult);
458
459             basePath.append(*baseFiles[i]);
460             comparisonPath.append(*comparisonFiles[j]);
461
462             drp->fBase.fFilename = *baseFiles[i];
463             drp->fBase.fFullPath = basePath;
464             drp->fBase.fStatus = DiffResource::kExists_Status;
465
466             drp->fComparison.fFilename = *comparisonFiles[j];
467             drp->fComparison.fFullPath = comparisonPath;
468             drp->fComparison.fStatus = DiffResource::kExists_Status;
469
470             SkAutoDataUnref baseFileBits(read_file(drp->fBase.fFullPath.c_str()));
471             if (baseFileBits) {
472                 drp->fBase.fStatus = DiffResource::kRead_Status;
473             }
474             SkAutoDataUnref comparisonFileBits(read_file(drp->fComparison.fFullPath.c_str()));
475             if (comparisonFileBits) {
476                 drp->fComparison.fStatus = DiffResource::kRead_Status;
477             }
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]);
482                 }
483                 if (NULL == comparisonFileBits) {
484                     drp->fComparison.fStatus = DiffResource::kCouldNotRead_Status;
485                     VERBOSE_STATUS("READ FAIL", ANSI_COLOR_RED, comparisonFiles[j]);
486                 }
487                 drp->fResult = DiffRecord::kCouldNotCompare_Result;
488
489             } else if (are_buffers_equal(baseFileBits, comparisonFileBits)) {
490                 drp->fResult = DiffRecord::kEqualBits_Result;
491                 VERBOSE_STATUS("MATCH", ANSI_COLOR_GREEN, baseFiles[i]);
492             } else {
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);
502                 } else {
503                     drp->fResult = DiffRecord::kCouldNotCompare_Result;
504                 }
505             }
506
507             ++i;
508             ++j;
509         }
510
511         if (getBounds) {
512             get_bounds(*drp);
513         }
514         SkASSERT(DiffRecord::kUnknown_Result != drp->fResult);
515         differences->push(drp);
516         summary->add(drp);
517     }
518
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;
526
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;
531
532         drp->fResult = DiffRecord::kCouldNotCompare_Result;
533         if (getBounds) {
534             get_bounds(*drp);
535         }
536         differences->push(drp);
537         summary->add(drp);
538     }
539
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;
547
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;
552
553         drp->fResult = DiffRecord::kCouldNotCompare_Result;
554         if (getBounds) {
555             get_bounds(*drp);
556         }
557         differences->push(drp);
558         summary->add(drp);
559     }
560
561     release_file_list(&baseFiles);
562     release_file_list(&comparisonFiles);
563 }
564
565 static void usage (char * argv0) {
566     SkDebugf("Skia baseline image diff tool\n");
567     SkDebugf("\n"
568 "Usage: \n"
569 "    %s <baseDir> <comparisonDir> [outputDir] \n", argv0);
570     SkDebugf(
571 "\nArguments:"
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"
597 "\n"
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"
602 "\n"
603 "\nIf no sort is specified, it will sort by fraction of pixels mismatching."
604 "\n");
605 }
606
607 const int kNoError = 0;
608 const int kGenericError = -1;
609
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>;
614
615     // Maximum error tolerated in any one color channel in any one pixel before
616     // a difference is reported.
617     int colorThreshold = 0;
618     SkString baseDir;
619     SkString comparisonDir;
620     SkString outputDir;
621
622     StringArray matchSubstrings;
623     StringArray nomatchSubstrings;
624
625     bool generateDiffs = true;
626     bool listFilenames = false;
627     bool printDirNames = true;
628     bool recurseIntoSubdirs = true;
629     bool verbose = false;
630     bool listFailingBase = false;
631
632     RecordArray differences;
633     DiffSummary summary;
634
635     bool failOnResultType[DiffRecord::kResultCount];
636     for (int i = 0; i < DiffRecord::kResultCount; i++) {
637         failOnResultType[i] = false;
638     }
639
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;
644         }
645     }
646
647     int i;
648     int numUnflaggedArguments = 0;
649     for (i = 1; i < argc; i++) {
650         if (!strcmp(argv[i], "--failonresult")) {
651             if (argc == ++i) {
652                 SkDebugf("failonresult expects one argument.\n");
653                 continue;
654             }
655             DiffRecord::Result type = DiffRecord::getResultByName(argv[i]);
656             if (type != DiffRecord::kResultCount) {
657                 failOnResultType[type] = true;
658             } else {
659                 SkDebugf("ignoring unrecognized result <%s>\n", argv[i]);
660             }
661             continue;
662         }
663         if (!strcmp(argv[i], "--failonstatus")) {
664             if (argc == ++i) {
665                 SkDebugf("failonstatus missing base status.\n");
666                 continue;
667             }
668             bool baseStatuses[DiffResource::kStatusCount];
669             if (!DiffResource::getMatchingStatuses(argv[i], baseStatuses)) {
670                 SkDebugf("unrecognized base status <%s>\n", argv[i]);
671             }
672
673             if (argc == ++i) {
674                 SkDebugf("failonstatus missing comparison status.\n");
675                 continue;
676             }
677             bool comparisonStatuses[DiffResource::kStatusCount];
678             if (!DiffResource::getMatchingStatuses(argv[i], comparisonStatuses)) {
679                 SkDebugf("unrecognized comarison status <%s>\n", argv[i]);
680             }
681
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];
686                 }
687             }
688             continue;
689         }
690         if (!strcmp(argv[i], "--help")) {
691             usage(argv[0]);
692             return kNoError;
693         }
694         if (!strcmp(argv[i], "--listfilenames")) {
695             listFilenames = true;
696             continue;
697         }
698         if (!strcmp(argv[i], "--verbose")) {
699             verbose = true;
700             continue;
701         }
702         if (!strcmp(argv[i], "--match")) {
703             matchSubstrings.push(new SkString(argv[++i]));
704             continue;
705         }
706         if (!strcmp(argv[i], "--nodiffs")) {
707             generateDiffs = false;
708             continue;
709         }
710         if (!strcmp(argv[i], "--nomatch")) {
711             nomatchSubstrings.push(new SkString(argv[++i]));
712             continue;
713         }
714         if (!strcmp(argv[i], "--noprintdirs")) {
715             printDirNames = false;
716             continue;
717         }
718         if (!strcmp(argv[i], "--norecurse")) {
719             recurseIntoSubdirs = false;
720             continue;
721         }
722         if (!strcmp(argv[i], "--sortbymaxmismatch")) {
723             sortProc = compare<CompareDiffMaxMismatches>;
724             continue;
725         }
726         if (!strcmp(argv[i], "--sortbymismatch")) {
727             sortProc = compare<CompareDiffMeanMismatches>;
728             continue;
729         }
730         if (!strcmp(argv[i], "--threshold")) {
731             colorThreshold = atoi(argv[++i]);
732             continue;
733         }
734         if (!strcmp(argv[i], "--weighted")) {
735             sortProc = compare<CompareDiffWeighted>;
736             continue;
737         }
738         if (argv[i][0] != '-') {
739             switch (numUnflaggedArguments++) {
740                 case 0:
741                     baseDir.set(argv[i]);
742                     continue;
743                 case 1:
744                     comparisonDir.set(argv[i]);
745                     continue;
746                 case 2:
747                     outputDir.set(argv[i]);
748                     continue;
749                 default:
750                     SkDebugf("extra unflagged argument <%s>\n", argv[i]);
751                     usage(argv[0]);
752                     return kGenericError;
753             }
754         }
755         if (!strcmp(argv[i], "--listFailingBase")) {
756             listFailingBase = true;
757             continue;
758         }
759
760         SkDebugf("Unrecognized argument <%s>\n", argv[i]);
761         usage(argv[0]);
762         return kGenericError;
763     }
764
765     if (numUnflaggedArguments == 2) {
766         outputDir = comparisonDir;
767     } else if (numUnflaggedArguments != 3) {
768         usage(argv[0]);
769         return kGenericError;
770     }
771
772     if (!baseDir.endsWith(PATH_DIV_STR)) {
773         baseDir.append(PATH_DIV_STR);
774     }
775     if (printDirNames) {
776         printf("baseDir is [%s]\n", baseDir.c_str());
777     }
778
779     if (!comparisonDir.endsWith(PATH_DIV_STR)) {
780         comparisonDir.append(PATH_DIV_STR);
781     }
782     if (printDirNames) {
783         printf("comparisonDir is [%s]\n", comparisonDir.c_str());
784     }
785
786     if (!outputDir.endsWith(PATH_DIV_STR)) {
787         outputDir.append(PATH_DIV_STR);
788     }
789     if (generateDiffs) {
790         if (printDirNames) {
791             printf("writing diffs to outputDir is [%s]\n", outputDir.c_str());
792         }
793     } else {
794         if (printDirNames) {
795             printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str());
796         }
797         outputDir.set("");
798     }
799
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(""));
804     }
805
806     create_diff_images(diffProc, colorThreshold, &differences,
807                        baseDir, comparisonDir, outputDir,
808                        matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, generateDiffs,
809                        verbose, &summary);
810     summary.print(listFilenames, failOnResultType, failOnStatusType);
811
812     if (listFailingBase) {
813         summary.printfFailingBaseNames("\n");
814     }
815
816     if (differences.count()) {
817         qsort(differences.begin(), differences.count(),
818               sizeof(DiffRecord*), sortProc);
819     }
820
821     if (generateDiffs) {
822         print_diff_page(summary.fNumMatches, colorThreshold, differences,
823                         baseDir, comparisonDir, outputDir);
824     }
825
826     for (i = 0; i < differences.count(); i++) {
827         delete differences[i];
828     }
829     matchSubstrings.deleteAll();
830     nomatchSubstrings.deleteAll();
831
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();
836         }
837     }
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();
843                 }
844             }
845         }
846     }
847
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;
852 }
853
854 #if !defined SK_BUILD_FOR_IOS
855 int main(int argc, char * const argv[]) {
856     return tool_main(argc, (char**) argv);
857 }
858 #endif