skdiff: recurse over subdirectories, unless --norecurse option is given
[platform/upstream/libSkiaSharp.git] / tools / skdiff_main.cpp
1
2 /*
3  * Copyright 2011 Google Inc.
4  *
5  * Use of this source code is governed by a BSD-style license that can be
6  * found in the LICENSE file.
7  */
8 #include "SkColorPriv.h"
9 #include "SkData.h"
10 #include "SkImageDecoder.h"
11 #include "SkImageEncoder.h"
12 #include "SkOSFile.h"
13 #include "SkStream.h"
14 #include "SkTDArray.h"
15 #include "SkTemplates.h"
16 #include "SkTime.h"
17 #include "SkTSearch.h"
18 #include "SkTypes.h"
19
20 /**
21  * skdiff
22  *
23  * Given three directory names, expects to find identically-named files in
24  * each of the first two; the first are treated as a set of baseline,
25  * the second a set of variant images, and a diff image is written into the
26  * third directory for each pair.
27  * Creates an index.html in the current third directory to compare each
28  * pair that does not match exactly.
29  * Recursively descends directories, unless run with --norecurse.
30  *
31  * Returns zero exit code if all images match across baseDir and comparisonDir.
32  */
33
34 #if SK_BUILD_FOR_WIN32
35     #define PATH_DIV_STR "\\"
36     #define PATH_DIV_CHAR '\\'
37 #else
38     #define PATH_DIV_STR "/"
39     #define PATH_DIV_CHAR '/'
40 #endif
41
42 // Result of comparison for each pair of files.
43 // Listed from "better" to "worse", for sorting of results.
44 enum Result {
45     kEqualBits,
46     kEqualPixels,
47     kDifferentPixels,
48     kDifferentSizes,
49     kDifferentOther,
50     kComparisonMissing,
51     kBaseMissing,
52     kUnknown,
53     //
54     kNumResultTypes  // NOT A VALID VALUE--used to set up arrays. Must be last.
55 };
56
57 // Returns the Result with this name.
58 // If there is no Result with this name, returns kNumResultTypes.
59 // TODO: Is there a better return value for the fall-through case?
60 Result getResultByName(const char *name) {
61     if (0 == strcmp("EqualBits", name)) {
62         return kEqualBits;
63     }
64     if (0 == strcmp("EqualPixels", name)) {
65         return kEqualPixels;
66     }
67     if (0 == strcmp("DifferentPixels", name)) {
68         return kDifferentPixels;
69     }
70     if (0 == strcmp("DifferentSizes", name)) {
71         return kDifferentSizes;
72     }
73     if (0 == strcmp("DifferentOther", name)) {
74         return kDifferentOther;
75     }
76     if (0 == strcmp("ComparisonMissing", name)) {
77         return kComparisonMissing;
78     }
79     if (0 == strcmp("BaseMissing", name)) {
80         return kBaseMissing;
81     }
82     if (0 == strcmp("Unknown", name)) {
83         return kUnknown;
84     }
85     return kNumResultTypes;
86 }
87
88 // Returns a text description of the given Result type.
89 const char *getResultDescription(Result result) {
90     switch (result) {
91       case kEqualBits:
92         return "contain exactly the same bits";
93       case kEqualPixels:
94         return "contain the same pixel values, but not the same bits";
95       case kDifferentPixels:
96         return "have identical dimensions but some differing pixels";
97       case kDifferentSizes:
98         return "have differing dimensions";
99       case kDifferentOther:
100         return "contain different bits and are not parsable images";
101       case kBaseMissing:
102         return "missing from baseDir";
103       case kComparisonMissing:
104         return "missing from comparisonDir";
105       case kUnknown:
106         return "not compared yet";
107       default:
108         return NULL;
109     }
110 }
111
112 struct DiffRecord {
113     DiffRecord (const SkString filename,
114                 const SkString basePath,
115                 const SkString comparisonPath,
116                 const Result result = kUnknown)
117         : fFilename (filename)
118         , fBasePath (basePath)
119         , fComparisonPath (comparisonPath)
120         , fBaseBitmap (new SkBitmap ())
121         , fComparisonBitmap (new SkBitmap ())
122         , fDifferenceBitmap (new SkBitmap ())
123         , fWhiteBitmap (new SkBitmap ())
124         , fBaseHeight (0)
125         , fBaseWidth (0)
126         , fFractionDifference (0)
127         , fWeightedFraction (0)
128         , fAverageMismatchR (0)
129         , fAverageMismatchG (0)
130         , fAverageMismatchB (0)
131         , fMaxMismatchR (0)
132         , fMaxMismatchG (0)
133         , fMaxMismatchB (0)
134         , fResult(result) {
135     };
136
137     SkString fFilename;
138     SkString fBasePath;
139     SkString fComparisonPath;
140
141     SkBitmap* fBaseBitmap;
142     SkBitmap* fComparisonBitmap;
143     SkBitmap* fDifferenceBitmap;
144     SkBitmap* fWhiteBitmap;
145
146     int fBaseHeight;
147     int fBaseWidth;
148
149     /// Arbitrary floating-point metric to be used to sort images from most
150     /// to least different from baseline; values of 0 will be omitted from the
151     /// summary webpage.
152     float fFractionDifference;
153     float fWeightedFraction;
154
155     float fAverageMismatchR;
156     float fAverageMismatchG;
157     float fAverageMismatchB;
158
159     uint32_t fMaxMismatchR;
160     uint32_t fMaxMismatchG;
161     uint32_t fMaxMismatchB;
162
163     /// Which category of diff result.
164     Result fResult;
165 };
166
167 #define MAX2(a,b) (((b) < (a)) ? (a) : (b))
168 #define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
169
170 const SkPMColor PMCOLOR_WHITE = SkPreMultiplyColor(SK_ColorWHITE);
171 const SkPMColor PMCOLOR_BLACK = SkPreMultiplyColor(SK_ColorBLACK);
172
173 typedef SkTDArray<SkString*> StringArray;
174 typedef StringArray FileArray;
175
176 struct DiffSummary {
177     DiffSummary ()
178         : fNumMatches (0)
179         , fNumMismatches (0)
180         , fMaxMismatchV (0)
181         , fMaxMismatchPercent (0) { };
182
183     ~DiffSummary() {
184         for (int i = 0; i < kNumResultTypes; i++) {
185             fResultsOfType[i].deleteAll();
186         }
187     }
188
189     uint32_t fNumMatches;
190     uint32_t fNumMismatches;
191     uint32_t fMaxMismatchV;
192     float fMaxMismatchPercent;
193
194     FileArray fResultsOfType[kNumResultTypes];
195
196     // Print a line about the contents of this FileArray to stdout.
197     void printContents(const FileArray& fileArray, const char* headerText, bool listFilenames) {
198         int n = fileArray.count();
199         printf("%d file pairs %s", n, headerText);
200         if (listFilenames) {
201             printf(": ");
202             for (int i = 0; i < n; ++i) {
203                 printf("%s ", fileArray[i]->c_str());
204             }
205         }
206         printf("\n");
207     }
208
209     void print(bool listFilenames, bool failOnResultType[kNumResultTypes]) {
210         printf("\ncompared %d file pairs:\n", fNumMatches + fNumMismatches);
211         for (int resultInt = 0; resultInt < kNumResultTypes; resultInt++) {
212             Result result = static_cast<Result>(resultInt);
213             if (failOnResultType[result]) {
214                 printf("[*] ");
215             } else {
216                 printf("[_] ");
217             }
218             printContents(fResultsOfType[result], getResultDescription(result), listFilenames);
219         }
220         printf("(results marked with [*] will cause nonzero return value)\n");
221         printf("\nnumber of mismatching file pairs: %d\n", fNumMismatches);
222         if (fNumMismatches > 0) {
223             printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
224             printf("Largest area mismatch was %.2f%% of pixels\n",fMaxMismatchPercent);
225         }
226     }
227
228     void add (DiffRecord* drp) {
229         uint32_t mismatchValue;
230
231         fResultsOfType[drp->fResult].push(new SkString(drp->fFilename));
232         switch (drp->fResult) {
233           case kEqualBits:
234             fNumMatches++;
235             break;
236           case kEqualPixels:
237             fNumMatches++;
238             break;
239           case kDifferentSizes:
240             fNumMismatches++;
241             break;
242           case kDifferentPixels:
243             fNumMismatches++;
244             if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
245                 fMaxMismatchPercent = drp->fFractionDifference * 100;
246             }
247             mismatchValue = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
248                                  drp->fMaxMismatchB);
249             if (mismatchValue > fMaxMismatchV) {
250                 fMaxMismatchV = mismatchValue;
251             }
252             break;
253           case kDifferentOther:
254             fNumMismatches++;
255             break;
256           case kBaseMissing:
257             fNumMismatches++;
258             break;
259           case kComparisonMissing:
260             fNumMismatches++;
261             break;
262           case kUnknown:
263             SkDEBUGFAIL("adding uncategorized DiffRecord");
264             break;
265           default:
266             SkDEBUGFAIL("adding DiffRecord with unhandled fResult value");
267             break;
268         }
269     }
270 };
271
272 typedef SkTDArray<DiffRecord*> RecordArray;
273
274 /// A wrapper for any sortProc (comparison routine) which applies a first-order
275 /// sort beforehand, and a tiebreaker if the sortProc returns 0.
276 template<typename T>
277 static int compare(const void* untyped_lhs, const void* untyped_rhs) {
278     const DiffRecord* lhs = *reinterpret_cast<DiffRecord* const*>(untyped_lhs);
279     const DiffRecord* rhs = *reinterpret_cast<DiffRecord* const*>(untyped_rhs);
280
281     // First-order sort... these comparisons should be applied before comparing
282     // pixel values, no matter what.
283     if (lhs->fResult != rhs->fResult) {
284         return (lhs->fResult < rhs->fResult) ? 1 : -1;
285     }
286
287     // Passed first-order sort, so call the pixel comparison routine.
288     int result = T::comparePixels(lhs, rhs);
289     if (result != 0) {
290         return result;
291     }
292
293     // Tiebreaker... if we got to this point, we don't really care
294     // which order they are sorted in, but let's at least be consistent.
295     return strcmp(lhs->fFilename.c_str(), rhs->fFilename.c_str());
296 }
297
298 /// Comparison routine for qsort;  sorts by fFractionDifference
299 /// from largest to smallest.
300 class CompareDiffMetrics {
301 public:
302     static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
303         if (lhs->fFractionDifference < rhs->fFractionDifference) {
304           return 1;
305         }
306         if (rhs->fFractionDifference < lhs->fFractionDifference) {
307           return -1;
308         }
309         return 0;
310     }
311 };
312
313 class CompareDiffWeighted {
314 public:
315     static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
316         if (lhs->fWeightedFraction < rhs->fWeightedFraction) {
317             return 1;
318         }
319         if (lhs->fWeightedFraction > rhs->fWeightedFraction) {
320             return -1;
321         }
322         return 0;
323     }
324 };
325
326 /// Comparison routine for qsort;  sorts by max(fAverageMismatch{RGB})
327 /// from largest to smallest.
328 class CompareDiffMeanMismatches {
329 public:
330     static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
331         float leftValue = MAX3(lhs->fAverageMismatchR,
332                                lhs->fAverageMismatchG,
333                                lhs->fAverageMismatchB);
334         float rightValue = MAX3(rhs->fAverageMismatchR,
335                                 rhs->fAverageMismatchG,
336                                 rhs->fAverageMismatchB);
337         if (leftValue < rightValue) {
338             return 1;
339         }
340         if (rightValue < leftValue) {
341             return -1;
342         }
343         return 0;
344     }
345 };
346
347 /// Comparison routine for qsort;  sorts by max(fMaxMismatch{RGB})
348 /// from largest to smallest.
349 class CompareDiffMaxMismatches {
350 public:
351     static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
352         uint32_t leftValue = MAX3(lhs->fMaxMismatchR,
353                                   lhs->fMaxMismatchG,
354                                   lhs->fMaxMismatchB);
355         uint32_t rightValue = MAX3(rhs->fMaxMismatchR,
356                                    rhs->fMaxMismatchG,
357                                    rhs->fMaxMismatchB);
358         if (leftValue < rightValue) {
359             return 1;
360         }
361         if (rightValue < leftValue) {
362             return -1;
363         }
364
365         return CompareDiffMeanMismatches::comparePixels(lhs, rhs);
366     }
367 };
368
369
370
371 /// Parameterized routine to compute the color of a pixel in a difference image.
372 typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
373
374 #if 0 // UNUSED
375 static void expand_and_copy (int width, int height, SkBitmap** dest) {
376     SkBitmap* temp = new SkBitmap ();
377     temp->reset();
378     temp->setConfig((*dest)->config(), width, height);
379     temp->allocPixels();
380     (*dest)->copyPixelsTo(temp->getPixels(), temp->getSize(),
381                           temp->rowBytes());
382     *dest = temp;
383 }
384 #endif
385
386 /// Returns true if the two buffers passed in are both non-NULL, and include
387 /// exactly the same byte values (and identical lengths).
388 static bool are_buffers_equal(SkData* skdata1, SkData* skdata2) {
389     if ((NULL == skdata1) || (NULL == skdata2)) {
390         return false;
391     }
392     if (skdata1->size() != skdata2->size()) {
393         return false;
394     }
395     return (0 == memcmp(skdata1->data(), skdata2->data(), skdata1->size()));
396 }
397
398 /// Reads the file at the given path and returns its complete contents as an
399 /// SkData object (or returns NULL on error).
400 static SkData* read_file(const char* file_path) {
401     SkFILEStream fileStream(file_path);
402     if (!fileStream.isValid()) {
403         SkDebugf("WARNING: could not open file <%s> for reading\n", file_path);
404         return NULL;
405     }
406     size_t bytesInFile = fileStream.getLength();
407     size_t bytesLeftToRead = bytesInFile;
408
409     void* bufferStart = sk_malloc_throw(bytesInFile);
410     char* bufferPointer = (char*)bufferStart;
411     while (bytesLeftToRead > 0) {
412         size_t bytesReadThisTime = fileStream.read(
413             bufferPointer, bytesLeftToRead);
414         if (0 == bytesReadThisTime) {
415             SkDebugf("WARNING: error reading from <%s>\n", file_path);
416             sk_free(bufferStart);
417             return NULL;
418         }
419         bytesLeftToRead -= bytesReadThisTime;
420         bufferPointer += bytesReadThisTime;
421     }
422     return SkData::NewFromMalloc(bufferStart, bytesInFile);
423 }
424
425 /// Decodes binary contents of baseFile and comparisonFile into
426 /// diffRecord->fBaseBitmap and diffRecord->fComparisonBitmap.
427 /// Returns true if that succeeds.
428 static bool get_bitmaps (SkData* baseFileContents,
429                          SkData* comparisonFileContents,
430                          DiffRecord* diffRecord) {
431     SkMemoryStream compareStream(comparisonFileContents->data(),
432                                  comparisonFileContents->size());
433     SkMemoryStream baseStream(baseFileContents->data(),
434                               baseFileContents->size());
435
436     SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
437     if (NULL == codec) {
438         SkDebugf("ERROR: no codec found for basePath <%s>\n",
439                  diffRecord->fBasePath.c_str());
440         return false;
441     }
442
443     // In debug, the DLL will automatically be unloaded when this is deleted,
444     // but that shouldn't be a problem in release mode.
445     SkAutoTDelete<SkImageDecoder> ad(codec);
446
447     baseStream.rewind();
448     if (!codec->decode(&baseStream, diffRecord->fBaseBitmap,
449                        SkBitmap::kARGB_8888_Config,
450                        SkImageDecoder::kDecodePixels_Mode)) {
451         SkDebugf("ERROR: codec failed for basePath <%s>\n",
452                  diffRecord->fBasePath.c_str());
453         return false;
454     }
455
456     diffRecord->fBaseWidth = diffRecord->fBaseBitmap->width();
457     diffRecord->fBaseHeight = diffRecord->fBaseBitmap->height();
458
459     if (!codec->decode(&compareStream, diffRecord->fComparisonBitmap,
460                        SkBitmap::kARGB_8888_Config,
461                        SkImageDecoder::kDecodePixels_Mode)) {
462         SkDebugf("ERROR: codec failed for comparisonPath <%s>\n",
463                  diffRecord->fComparisonPath.c_str());
464         return false;
465     }
466
467     return true;
468 }
469
470 static bool get_bitmap_height_width(const SkString& path,
471                                     int *height, int *width) {
472     SkFILEStream stream(path.c_str());
473     if (!stream.isValid()) {
474         SkDebugf("ERROR: couldn't open file <%s>\n",
475                  path.c_str());
476         return false;
477     }
478
479     SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
480     if (NULL == codec) {
481         SkDebugf("ERROR: no codec found for <%s>\n",
482                  path.c_str());
483         return false;
484     }
485
486     SkAutoTDelete<SkImageDecoder> ad(codec);
487     SkBitmap bm;
488
489     stream.rewind();
490     if (!codec->decode(&stream, &bm,
491                        SkBitmap::kARGB_8888_Config,
492                        SkImageDecoder::kDecodePixels_Mode)) {
493         SkDebugf("ERROR: codec failed for <%s>\n",
494                  path.c_str());
495         return false;
496     }
497
498     *height = bm.height();
499     *width = bm.width();
500
501     return true;
502 }
503
504 // from gm - thanks to PNG, we need to force all pixels 100% opaque
505 static void force_all_opaque(const SkBitmap& bitmap) {
506    SkAutoLockPixels lock(bitmap);
507    for (int y = 0; y < bitmap.height(); y++) {
508        for (int x = 0; x < bitmap.width(); x++) {
509            *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
510        }
511    }
512 }
513
514 // from gm
515 static bool write_bitmap(const SkString& path, const SkBitmap* bitmap) {
516     SkBitmap copy;
517     bitmap->copyTo(&copy, SkBitmap::kARGB_8888_Config);
518     force_all_opaque(copy);
519     return SkImageEncoder::EncodeFile(path.c_str(), copy,
520                                       SkImageEncoder::kPNG_Type, 100);
521 }
522
523 // from gm
524 static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
525     int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
526     int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
527     int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
528
529     return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
530 }
531
532 static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1,
533                                             const int threshold) {
534     int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
535     int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
536     int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
537     int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
538
539     return ((SkAbs32(da) <= threshold) &&
540             (SkAbs32(dr) <= threshold) &&
541             (SkAbs32(dg) <= threshold) &&
542             (SkAbs32(db) <= threshold));
543 }
544
545 // based on gm
546 // Postcondition: when we exit this method, dr->fResult should have some value
547 // other than kUnknown.
548 static void compute_diff(DiffRecord* dr,
549                          DiffMetricProc diffFunction,
550                          const int colorThreshold) {
551     SkAutoLockPixels alpDiff(*dr->fDifferenceBitmap);
552     SkAutoLockPixels alpWhite(*dr->fWhiteBitmap);
553
554     const int w = dr->fComparisonBitmap->width();
555     const int h = dr->fComparisonBitmap->height();
556     int mismatchedPixels = 0;
557     int totalMismatchR = 0;
558     int totalMismatchG = 0;
559     int totalMismatchB = 0;
560
561     if (w != dr->fBaseWidth || h != dr->fBaseHeight) {
562         dr->fResult = kDifferentSizes;
563         return;
564     }
565     // Accumulate fractionally different pixels, then divide out
566     // # of pixels at the end.
567     dr->fWeightedFraction = 0;
568     for (int y = 0; y < h; y++) {
569         for (int x = 0; x < w; x++) {
570             SkPMColor c0 = *dr->fBaseBitmap->getAddr32(x, y);
571             SkPMColor c1 = *dr->fComparisonBitmap->getAddr32(x, y);
572             SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
573             SkPMColor outputDifference = diffFunction(c0, c1);
574             uint32_t thisR = SkGetPackedR32(trueDifference);
575             uint32_t thisG = SkGetPackedG32(trueDifference);
576             uint32_t thisB = SkGetPackedB32(trueDifference);
577             totalMismatchR += thisR;
578             totalMismatchG += thisG;
579             totalMismatchB += thisB;
580             // In HSV, value is defined as max RGB component.
581             int value = MAX3(thisR, thisG, thisB);
582             dr->fWeightedFraction += ((float) value) / 255;
583             if (thisR > dr->fMaxMismatchR) {
584                 dr->fMaxMismatchR = thisR;
585             }
586             if (thisG > dr->fMaxMismatchG) {
587                 dr->fMaxMismatchG = thisG;
588             }
589             if (thisB > dr->fMaxMismatchB) {
590                 dr->fMaxMismatchB = thisB;
591             }
592             if (!colors_match_thresholded(c0, c1, colorThreshold)) {
593                 mismatchedPixels++;
594                 *dr->fDifferenceBitmap->getAddr32(x, y) = outputDifference;
595                 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_WHITE;
596             } else {
597                 *dr->fDifferenceBitmap->getAddr32(x, y) = 0;
598                 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_BLACK;
599             }
600         }
601     }
602     if (0 == mismatchedPixels) {
603         dr->fResult = kEqualPixels;
604         return;
605     }
606     dr->fResult = kDifferentPixels;
607     int pixelCount = w * h;
608     dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
609     dr->fWeightedFraction /= pixelCount;
610     dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
611     dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
612     dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
613 }
614
615 /// Return a copy of the "input" string, within which we have replaced all instances
616 /// of oldSubstring with newSubstring.
617 ///
618 /// TODO: If we like this, we should move it into the core SkString implementation,
619 /// adding more checks and ample test cases, and paying more attention to efficiency.
620 static SkString replace_all(const SkString &input,
621                             const char oldSubstring[], const char newSubstring[]) {
622     SkString output;
623     const char *input_cstr = input.c_str();
624     const char *first_char = input_cstr;
625     const char *match_char;
626     int oldSubstringLen = strlen(oldSubstring);
627     while (NULL != (match_char = strstr(first_char, oldSubstring))) {
628         output.append(first_char, (match_char - first_char));
629         output.append(newSubstring);
630         first_char = match_char + oldSubstringLen;
631     }
632     output.append(first_char);
633     return output;
634 }
635
636 static SkString filename_to_derived_filename (const SkString& filename,
637                                               const char *suffix) {
638     SkString diffName (filename);
639     const char* cstring = diffName.c_str();
640     int dotOffset = strrchr(cstring, '.') - cstring;
641     diffName.remove(dotOffset, diffName.size() - dotOffset);
642     diffName.append(suffix);
643
644     // In case we recursed into subdirectories, replace slashes with something else
645     // so the diffs will all be written into a single flat directory.
646     diffName = replace_all(diffName, PATH_DIV_STR, "_");
647     return diffName;
648 }
649
650 /// Given a image filename, returns the name of the file containing the
651 /// associated difference image.
652 static SkString filename_to_diff_filename (const SkString& filename) {
653     return filename_to_derived_filename(filename, "-diff.png");
654 }
655
656 /// Given a image filename, returns the name of the file containing the
657 /// "white" difference image.
658 static SkString filename_to_white_filename (const SkString& filename) {
659     return filename_to_derived_filename(filename, "-white.png");
660 }
661
662 static void release_bitmaps(DiffRecord* drp) {
663     delete drp->fBaseBitmap;
664     drp->fBaseBitmap = NULL;
665     delete drp->fComparisonBitmap;
666     drp->fComparisonBitmap = NULL;
667     delete drp->fDifferenceBitmap;
668     drp->fDifferenceBitmap = NULL;
669     delete drp->fWhiteBitmap;
670     drp->fWhiteBitmap = NULL;
671 }
672
673
674 /// If outputDir.isEmpty(), don't write out diff files.
675 static void create_and_write_diff_image(DiffRecord* drp,
676                                         DiffMetricProc dmp,
677                                         const int colorThreshold,
678                                         const SkString& outputDir,
679                                         const SkString& filename) {
680     const int w = drp->fBaseWidth;
681     const int h = drp->fBaseHeight;
682     drp->fDifferenceBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
683     drp->fDifferenceBitmap->allocPixels();
684     drp->fWhiteBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
685     drp->fWhiteBitmap->allocPixels();
686
687     SkASSERT(kUnknown == drp->fResult);
688     compute_diff(drp, dmp, colorThreshold);
689     SkASSERT(kUnknown != drp->fResult);
690
691     if ((kDifferentPixels == drp->fResult) && !outputDir.isEmpty()) {
692         SkString differencePath (outputDir);
693         differencePath.append(filename_to_diff_filename(filename));
694         write_bitmap(differencePath, drp->fDifferenceBitmap);
695         SkString whitePath (outputDir);
696         whitePath.append(filename_to_white_filename(filename));
697         write_bitmap(whitePath, drp->fWhiteBitmap);
698     }
699
700     release_bitmaps(drp);
701 }
702
703 /// Returns true if string contains any of these substrings.
704 static bool string_contains_any_of(const SkString& string,
705                                    const StringArray& substrings) {
706     for (int i = 0; i < substrings.count(); i++) {
707         if (string.contains(substrings[i]->c_str())) {
708             return true;
709         }
710     }
711     return false;
712 }
713
714 /// Internal (potentially recursive) implementation of get_file_list.
715 static void get_file_list_subdir(const SkString& rootDir, const SkString& subDir,
716                                  const StringArray& matchSubstrings,
717                                  const StringArray& nomatchSubstrings,
718                                  bool recurseIntoSubdirs, FileArray *files) {
719     bool isSubDirEmpty = subDir.isEmpty();
720     SkString dir(rootDir);
721     if (!isSubDirEmpty) {
722         dir.append(PATH_DIV_STR);
723         dir.append(subDir);
724     }
725
726     // Iterate over files (not directories) within dir.
727     SkOSFile::Iter fileIterator(dir.c_str());
728     SkString fileName;
729     while (fileIterator.next(&fileName, false)) {
730         if (fileName.startsWith(".")) {
731             continue;
732         }
733         SkString pathRelativeToRootDir(subDir);
734         if (!isSubDirEmpty) {
735             pathRelativeToRootDir.append(PATH_DIV_STR);
736         }
737         pathRelativeToRootDir.append(fileName);
738         if (string_contains_any_of(pathRelativeToRootDir, matchSubstrings) &&
739             !string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) {
740             files->push(new SkString(pathRelativeToRootDir));
741         }
742     }
743
744     // Recurse into any non-ignored subdirectories.
745     if (recurseIntoSubdirs) {
746         SkOSFile::Iter dirIterator(dir.c_str());
747         SkString dirName;
748         while (dirIterator.next(&dirName, true)) {
749             if (dirName.startsWith(".")) {
750                 continue;
751             }
752             SkString pathRelativeToRootDir(subDir);
753             if (!isSubDirEmpty) {
754                 pathRelativeToRootDir.append(PATH_DIV_STR);
755             }
756             pathRelativeToRootDir.append(dirName);
757             if (!string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) {
758                 get_file_list_subdir(rootDir, pathRelativeToRootDir,
759                                      matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
760                                      files);
761             }
762         }
763     }
764 }
765
766 /// Iterate over dir and get all files whose filename:
767 ///  - matches any of the substrings in matchSubstrings, but...
768 ///  - DOES NOT match any of the substrings in nomatchSubstrings
769 ///  - DOES NOT start with a dot (.)
770 /// Adds the matching files to the list in *files.
771 static void get_file_list(const SkString& dir,
772                           const StringArray& matchSubstrings,
773                           const StringArray& nomatchSubstrings,
774                           bool recurseIntoSubdirs, FileArray *files) {
775     get_file_list_subdir(dir, SkString(""),
776                          matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
777                          files);
778 }
779
780 static void release_file_list(FileArray *files) {
781     files->deleteAll();
782 }
783
784 /// Comparison routines for qsort, sort by file names.
785 static int compare_file_name_metrics(SkString **lhs, SkString **rhs) {
786     return strcmp((*lhs)->c_str(), (*rhs)->c_str());
787 }
788
789 /// Creates difference images, returns the number that have a 0 metric.
790 /// If outputDir.isEmpty(), don't write out diff files.
791 static void create_diff_images (DiffMetricProc dmp,
792                                 const int colorThreshold,
793                                 RecordArray* differences,
794                                 const SkString& baseDir,
795                                 const SkString& comparisonDir,
796                                 const SkString& outputDir,
797                                 const StringArray& matchSubstrings,
798                                 const StringArray& nomatchSubstrings,
799                                 bool recurseIntoSubdirs,
800                                 DiffSummary* summary) {
801     SkASSERT(!baseDir.isEmpty());
802     SkASSERT(!comparisonDir.isEmpty());
803
804     FileArray baseFiles;
805     FileArray comparisonFiles;
806
807     get_file_list(baseDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, &baseFiles);
808     get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs,
809                   &comparisonFiles);
810
811     if (!baseFiles.isEmpty()) {
812         qsort(baseFiles.begin(), baseFiles.count(), sizeof(SkString*),
813               SkCastForQSort(compare_file_name_metrics));
814     }
815     if (!comparisonFiles.isEmpty()) {
816         qsort(comparisonFiles.begin(), comparisonFiles.count(),
817               sizeof(SkString*), SkCastForQSort(compare_file_name_metrics));
818     }
819
820     int i = 0;
821     int j = 0;
822
823     while (i < baseFiles.count() &&
824            j < comparisonFiles.count()) {
825
826         SkString basePath (baseDir);
827         basePath.append(*baseFiles[i]);
828         SkString comparisonPath (comparisonDir);
829         comparisonPath.append(*comparisonFiles[j]);
830
831         DiffRecord *drp = NULL;
832         int v = strcmp(baseFiles[i]->c_str(),
833                        comparisonFiles[j]->c_str());
834
835         if (v < 0) {
836             // in baseDir, but not in comparisonDir
837             drp = new DiffRecord(*baseFiles[i], basePath, comparisonPath,
838                                  kComparisonMissing);
839             ++i;
840         } else if (v > 0) {
841             // in comparisonDir, but not in baseDir
842             drp = new DiffRecord(*comparisonFiles[j], basePath, comparisonPath,
843                                  kBaseMissing);
844             ++j;
845         } else {
846             // Found the same filename in both baseDir and comparisonDir.
847             drp = new DiffRecord(*baseFiles[i], basePath, comparisonPath);
848             SkASSERT(kUnknown == drp->fResult);
849
850             SkData* baseFileBits = NULL;
851             SkData* comparisonFileBits = NULL;
852             if (NULL == (baseFileBits = read_file(basePath.c_str()))) {
853                 SkDebugf("WARNING: couldn't read base file <%s>\n",
854                          basePath.c_str());
855                 drp->fResult = kBaseMissing;
856             } else if (NULL == (comparisonFileBits = read_file(comparisonPath.c_str()))) {
857                 SkDebugf("WARNING: couldn't read comparison file <%s>\n",
858                          comparisonPath.c_str());
859                 drp->fResult = kComparisonMissing;
860             } else {
861                 if (are_buffers_equal(baseFileBits, comparisonFileBits)) {
862                     drp->fResult = kEqualBits;
863                 } else if (get_bitmaps(baseFileBits, comparisonFileBits, drp)) {
864                     create_and_write_diff_image(drp, dmp, colorThreshold,
865                                                 outputDir, *baseFiles[i]);
866                 } else {
867                     drp->fResult = kDifferentOther;
868                 }
869             }
870             if (baseFileBits) {
871                 baseFileBits->unref();
872             }
873             if (comparisonFileBits) {
874                 comparisonFileBits->unref();
875             }
876             ++i;
877             ++j;
878         }
879         SkASSERT(kUnknown != drp->fResult);
880         differences->push(drp);
881         summary->add(drp);
882     }
883
884     for (; i < baseFiles.count(); ++i) {
885         // files only in baseDir
886         SkString basePath (baseDir);
887         basePath.append(*baseFiles[i]);
888         SkString comparisonPath;
889         DiffRecord *drp = new DiffRecord(*baseFiles[i], basePath,
890                                          comparisonPath, kComparisonMissing);
891         differences->push(drp);
892         summary->add(drp);
893     }
894
895     for (; j < comparisonFiles.count(); ++j) {
896         // files only in comparisonDir
897         SkString basePath;
898         SkString comparisonPath(comparisonDir);
899         comparisonPath.append(*comparisonFiles[j]);
900         DiffRecord *drp = new DiffRecord(*comparisonFiles[j], basePath,
901                                          comparisonPath, kBaseMissing);
902         differences->push(drp);
903         summary->add(drp);
904     }
905
906     release_file_list(&baseFiles);
907     release_file_list(&comparisonFiles);
908 }
909
910 /// Make layout more consistent by scaling image to 240 height, 360 width,
911 /// or natural size, whichever is smallest.
912 static int compute_image_height (int height, int width) {
913     int retval = 240;
914     if (height < retval) {
915         retval = height;
916     }
917     float scale = (float) retval / height;
918     if (width * scale > 360) {
919         scale = (float) 360 / width;
920         retval = static_cast<int>(height * scale);
921     }
922     return retval;
923 }
924
925 static void print_table_header (SkFILEWStream* stream,
926                                 const int matchCount,
927                                 const int colorThreshold,
928                                 const RecordArray& differences,
929                                 const SkString &baseDir,
930                                 const SkString &comparisonDir,
931                                 bool doOutputDate=false) {
932     stream->writeText("<table>\n");
933     stream->writeText("<tr><th>");
934     if (doOutputDate) {
935         SkTime::DateTime dt;
936         SkTime::GetDateTime(&dt);
937         stream->writeText("SkDiff run at ");
938         stream->writeDecAsText(dt.fHour);
939         stream->writeText(":");
940         if (dt.fMinute < 10) {
941             stream->writeText("0");
942         }
943         stream->writeDecAsText(dt.fMinute);
944         stream->writeText(":");
945         if (dt.fSecond < 10) {
946             stream->writeText("0");
947         }
948         stream->writeDecAsText(dt.fSecond);
949         stream->writeText("<br>");
950     }
951     stream->writeDecAsText(matchCount);
952     stream->writeText(" of ");
953     stream->writeDecAsText(differences.count());
954     stream->writeText(" images matched ");
955     if (colorThreshold == 0) {
956         stream->writeText("exactly");
957     } else {
958         stream->writeText("within ");
959         stream->writeDecAsText(colorThreshold);
960         stream->writeText(" color units per component");
961     }
962     stream->writeText(".<br>");
963     stream->writeText("</th>\n<th>");
964     stream->writeText("every different pixel shown in white");
965     stream->writeText("</th>\n<th>");
966     stream->writeText("color difference at each pixel");
967     stream->writeText("</th>\n<th>baseDir: ");
968     stream->writeText(baseDir.c_str());
969     stream->writeText("</th>\n<th>comparisonDir: ");
970     stream->writeText(comparisonDir.c_str());
971     stream->writeText("</th>\n");
972     stream->writeText("</tr>\n");
973 }
974
975 static void print_pixel_count (SkFILEWStream* stream,
976                                const DiffRecord& diff) {
977     stream->writeText("<br>(");
978     stream->writeDecAsText(static_cast<int>(diff.fFractionDifference *
979                                             diff.fBaseWidth *
980                                             diff.fBaseHeight));
981     stream->writeText(" pixels)");
982 /*
983     stream->writeDecAsText(diff.fWeightedFraction *
984                            diff.fBaseWidth *
985                            diff.fBaseHeight);
986     stream->writeText(" weighted pixels)");
987 */
988 }
989
990 static void print_label_cell (SkFILEWStream* stream,
991                               const DiffRecord& diff) {
992     char metricBuf [20];
993
994     stream->writeText("<td><b>");
995     stream->writeText(diff.fFilename.c_str());
996     stream->writeText("</b><br>");
997     switch (diff.fResult) {
998       case kEqualBits:
999         SkDEBUGFAIL("should not encounter DiffRecord with kEqualBits here");
1000         return;
1001       case kEqualPixels:
1002         SkDEBUGFAIL("should not encounter DiffRecord with kEqualPixels here");
1003         return;
1004       case kDifferentSizes:
1005         stream->writeText("Image sizes differ</td>");
1006         return;
1007       case kDifferentPixels:
1008         sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
1009         stream->writeText(metricBuf);
1010         stream->writeText(" of pixels differ");
1011         stream->writeText("\n  (");
1012         sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
1013         stream->writeText(metricBuf);
1014         stream->writeText(" weighted)");
1015         // Write the actual number of pixels that differ if it's < 1%
1016         if (diff.fFractionDifference < 0.01) {
1017             print_pixel_count(stream, diff);
1018         }
1019         stream->writeText("<br>Average color mismatch ");
1020         stream->writeDecAsText(static_cast<int>(MAX3(diff.fAverageMismatchR,
1021                                                      diff.fAverageMismatchG,
1022                                                      diff.fAverageMismatchB)));
1023         stream->writeText("<br>Max color mismatch ");
1024         stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
1025                                     diff.fMaxMismatchG,
1026                                     diff.fMaxMismatchB));
1027         stream->writeText("</td>");
1028         break;
1029       case kDifferentOther:
1030         stream->writeText("Files differ; unable to parse one or both files</td>");
1031         return;
1032       case kBaseMissing:
1033         stream->writeText("Missing from baseDir</td>");
1034         return;
1035       case kComparisonMissing:
1036         stream->writeText("Missing from comparisonDir</td>");
1037         return;
1038       default:
1039         SkDEBUGFAIL("encountered DiffRecord with unknown result type");
1040         return;
1041     }
1042 }
1043
1044 static void print_image_cell (SkFILEWStream* stream,
1045                               const SkString& path,
1046                               int height) {
1047     stream->writeText("<td><a href=\"");
1048     stream->writeText(path.c_str());
1049     stream->writeText("\"><img src=\"");
1050     stream->writeText(path.c_str());
1051     stream->writeText("\" height=\"");
1052     stream->writeDecAsText(height);
1053     stream->writeText("px\"></a></td>");
1054 }
1055
1056 #if 0 // UNUSED
1057 static void print_text_cell (SkFILEWStream* stream, const char* text) {
1058     stream->writeText("<td align=center>");
1059     if (NULL != text) {
1060         stream->writeText(text);
1061     }
1062     stream->writeText("</td>");
1063 }
1064 #endif
1065
1066 static void print_diff_with_missing_file(SkFILEWStream* stream,
1067                                          DiffRecord& diff,
1068                                          const SkString& relativePath) {
1069     stream->writeText("<tr>\n");
1070     print_label_cell(stream, diff);
1071     stream->writeText("<td>N/A</td>");
1072     stream->writeText("<td>N/A</td>");
1073     if (kBaseMissing != diff.fResult) {
1074         int h, w;
1075         if (!get_bitmap_height_width(diff.fBasePath, &h, &w)) {
1076             stream->writeText("<td>N/A</td>");
1077         } else {
1078             int height = compute_image_height(h, w);
1079             if (!diff.fBasePath.startsWith(PATH_DIV_STR)) {
1080                 diff.fBasePath.prepend(relativePath);
1081             }
1082             print_image_cell(stream, diff.fBasePath, height);
1083         }
1084     } else {
1085         stream->writeText("<td>N/A</td>");
1086     }
1087     if (kComparisonMissing != diff.fResult) {
1088         int h, w;
1089         if (!get_bitmap_height_width(diff.fComparisonPath, &h, &w)) {
1090             stream->writeText("<td>N/A</td>");
1091         } else {
1092             int height = compute_image_height(h, w);
1093             if (!diff.fComparisonPath.startsWith(PATH_DIV_STR)) {
1094                 diff.fComparisonPath.prepend(relativePath);
1095             }
1096             print_image_cell(stream, diff.fComparisonPath, height);
1097         }
1098     } else {
1099         stream->writeText("<td>N/A</td>");
1100     }
1101     stream->writeText("</tr>\n");
1102     stream->flush();
1103 }
1104
1105 static void print_diff_page (const int matchCount,
1106                              const int colorThreshold,
1107                              const RecordArray& differences,
1108                              const SkString& baseDir,
1109                              const SkString& comparisonDir,
1110                              const SkString& outputDir) {
1111
1112     SkASSERT(!baseDir.isEmpty());
1113     SkASSERT(!comparisonDir.isEmpty());
1114     SkASSERT(!outputDir.isEmpty());
1115
1116     SkString outputPath (outputDir);
1117     outputPath.append("index.html");
1118     //SkFILEWStream outputStream ("index.html");
1119     SkFILEWStream outputStream (outputPath.c_str());
1120
1121     // Need to convert paths from relative-to-cwd to relative-to-outputDir
1122     // FIXME this doesn't work if there are '..' inside the outputDir
1123
1124     bool isPathAbsolute = false;
1125     // On Windows or Linux, a path starting with PATH_DIV_CHAR is absolute.
1126     if (outputDir.size() > 0 && PATH_DIV_CHAR == outputDir[0]) {
1127         isPathAbsolute = true;
1128     }
1129 #ifdef SK_BUILD_FOR_WIN32
1130     // On Windows, absolute paths can also start with "x:", where x is any
1131     // drive letter.
1132     if (outputDir.size() > 1 && ':' == outputDir[1]) {
1133         isPathAbsolute = true;
1134     }
1135 #endif
1136
1137     SkString relativePath;
1138     if (!isPathAbsolute) {
1139         unsigned int ui;
1140         for (ui = 0; ui < outputDir.size(); ui++) {
1141             if (outputDir[ui] == PATH_DIV_CHAR) {
1142                 relativePath.append(".." PATH_DIV_STR);
1143             }
1144         }
1145     }
1146
1147     outputStream.writeText("<html>\n<body>\n");
1148     print_table_header(&outputStream, matchCount, colorThreshold, differences,
1149                        baseDir, comparisonDir);
1150     int i;
1151     for (i = 0; i < differences.count(); i++) {
1152         DiffRecord* diff = differences[i];
1153
1154         switch (diff->fResult) {
1155           // Cases in which there is no diff to report.
1156           case kEqualBits:
1157           case kEqualPixels:
1158             continue;
1159           // Cases in which we want a detailed pixel diff.
1160           case kDifferentPixels:
1161             break;
1162           // Cases in which the files differed, but we can't display the diff.
1163           case kDifferentSizes:
1164           case kDifferentOther:
1165           case kBaseMissing:
1166           case kComparisonMissing:
1167             print_diff_with_missing_file(&outputStream, *diff, relativePath);
1168             continue;
1169           default:
1170             SkDEBUGFAIL("encountered DiffRecord with unknown result type");
1171             continue;
1172         }
1173
1174         if (!diff->fBasePath.startsWith(PATH_DIV_STR)) {
1175             diff->fBasePath.prepend(relativePath);
1176         }
1177         if (!diff->fComparisonPath.startsWith(PATH_DIV_STR)) {
1178             diff->fComparisonPath.prepend(relativePath);
1179         }
1180
1181         int height = compute_image_height(diff->fBaseHeight, diff->fBaseWidth);
1182         outputStream.writeText("<tr>\n");
1183         print_label_cell(&outputStream, *diff);
1184         print_image_cell(&outputStream,
1185                          filename_to_white_filename(diff->fFilename), height);
1186         print_image_cell(&outputStream,
1187                          filename_to_diff_filename(diff->fFilename), height);
1188         print_image_cell(&outputStream, diff->fBasePath, height);
1189         print_image_cell(&outputStream, diff->fComparisonPath, height);
1190         outputStream.writeText("</tr>\n");
1191         outputStream.flush();
1192     }
1193     outputStream.writeText("</table>\n");
1194     outputStream.writeText("</body>\n</html>\n");
1195     outputStream.flush();
1196 }
1197
1198 static void usage (char * argv0) {
1199     SkDebugf("Skia baseline image diff tool\n");
1200     SkDebugf("\n"
1201 "Usage: \n"
1202 "    %s <baseDir> <comparisonDir> [outputDir] \n"
1203 , argv0, argv0);
1204     SkDebugf(
1205 "\nArguments:"
1206 "\n    --failonresult <result>: After comparing all file pairs, exit with nonzero"
1207 "\n                             return code (number of file pairs yielding this"
1208 "\n                             result) if any file pairs yielded this result."
1209 "\n                             This flag may be repeated, in which case the"
1210 "\n                             return code will be the number of fail pairs"
1211 "\n                             yielding ANY of these results."
1212 "\n    --help: display this info"
1213 "\n    --listfilenames: list all filenames for each result type in stdout"
1214 "\n    --match <substring>: compare files whose filenames contain this substring;"
1215 "\n                         if unspecified, compare ALL files."
1216 "\n                         this flag may be repeated."
1217 "\n    --nodiffs: don't write out image diffs or index.html, just generate"
1218 "\n               report on stdout"
1219 "\n    --nomatch <substring>: regardless of --match, DO NOT compare files whose"
1220 "\n                           filenames contain this substring."
1221 "\n                           this flag may be repeated."
1222 "\n    --noprintdirs: do not print the directories used."
1223 "\n    --norecurse: do not recurse into subdirectories."
1224 "\n    --sortbymaxmismatch: sort by worst color channel mismatch;"
1225 "\n                         break ties with -sortbymismatch"
1226 "\n    --sortbymismatch: sort by average color channel mismatch"
1227 "\n    --threshold <n>: only report differences > n (per color channel) [default 0]"
1228 "\n    --weighted: sort by # pixels different weighted by color difference"
1229 "\n"
1230 "\n    baseDir: directory to read baseline images from."
1231 "\n    comparisonDir: directory to read comparison images from"
1232 "\n    outputDir: directory to write difference images and index.html to;"
1233 "\n               defaults to comparisonDir"
1234 "\n"
1235 "\nIf no sort is specified, it will sort by fraction of pixels mismatching."
1236 "\n");
1237 }
1238
1239 const int kNoError = 0;
1240 const int kGenericError = -1;
1241
1242 int main (int argc, char ** argv) {
1243     DiffMetricProc diffProc = compute_diff_pmcolor;
1244     int (*sortProc)(const void*, const void*) = compare<CompareDiffMetrics>;
1245
1246     // Maximum error tolerated in any one color channel in any one pixel before
1247     // a difference is reported.
1248     int colorThreshold = 0;
1249     SkString baseDir;
1250     SkString comparisonDir;
1251     SkString outputDir;
1252
1253     StringArray matchSubstrings;
1254     StringArray nomatchSubstrings;
1255
1256     bool generateDiffs = true;
1257     bool listFilenames = false;
1258     bool printDirNames = true;
1259     bool recurseIntoSubdirs = true;
1260
1261     RecordArray differences;
1262     DiffSummary summary;
1263
1264     bool failOnResultType[kNumResultTypes];
1265     for (int i = 0; i < kNumResultTypes; i++) {
1266         failOnResultType[i] = false;
1267     }
1268
1269     int i;
1270     int numUnflaggedArguments = 0;
1271     for (i = 1; i < argc; i++) {
1272         if (!strcmp(argv[i], "--failonresult")) {
1273             Result type = getResultByName(argv[++i]);
1274             failOnResultType[type] = true;
1275             continue;
1276         }
1277         if (!strcmp(argv[i], "--help")) {
1278             usage(argv[0]);
1279             return kNoError;
1280         }
1281         if (!strcmp(argv[i], "--listfilenames")) {
1282             listFilenames = true;
1283             continue;
1284         }
1285         if (!strcmp(argv[i], "--match")) {
1286             matchSubstrings.push(new SkString(argv[++i]));
1287             continue;
1288         }
1289         if (!strcmp(argv[i], "--nodiffs")) {
1290             generateDiffs = false;
1291             continue;
1292         }
1293         if (!strcmp(argv[i], "--nomatch")) {
1294             nomatchSubstrings.push(new SkString(argv[++i]));
1295             continue;
1296         }
1297         if (!strcmp(argv[i], "--noprintdirs")) {
1298             printDirNames = false;
1299             continue;
1300         }
1301         if (!strcmp(argv[i], "--norecurse")) {
1302             recurseIntoSubdirs = false;
1303             continue;
1304         }
1305         if (!strcmp(argv[i], "--sortbymaxmismatch")) {
1306             sortProc = compare<CompareDiffMaxMismatches>;
1307             continue;
1308         }
1309         if (!strcmp(argv[i], "--sortbymismatch")) {
1310             sortProc = compare<CompareDiffMeanMismatches>;
1311             continue;
1312         }
1313         if (!strcmp(argv[i], "--threshold")) {
1314             colorThreshold = atoi(argv[++i]);
1315             continue;
1316         }
1317         if (!strcmp(argv[i], "--weighted")) {
1318             sortProc = compare<CompareDiffWeighted>;
1319             continue;
1320         }
1321         if (argv[i][0] != '-') {
1322             switch (numUnflaggedArguments++) {
1323                 case 0:
1324                     baseDir.set(argv[i]);
1325                     continue;
1326                 case 1:
1327                     comparisonDir.set(argv[i]);
1328                     continue;
1329                 case 2:
1330                     outputDir.set(argv[i]);
1331                     continue;
1332                 default:
1333                     SkDebugf("extra unflagged argument <%s>\n", argv[i]);
1334                     usage(argv[0]);
1335                     return kGenericError;
1336             }
1337         }
1338
1339         SkDebugf("Unrecognized argument <%s>\n", argv[i]);
1340         usage(argv[0]);
1341         return kGenericError;
1342     }
1343
1344     if (numUnflaggedArguments == 2) {
1345         outputDir = comparisonDir;
1346     } else if (numUnflaggedArguments != 3) {
1347         usage(argv[0]);
1348         return kGenericError;
1349     }
1350
1351     if (!baseDir.endsWith(PATH_DIV_STR)) {
1352         baseDir.append(PATH_DIV_STR);
1353     }
1354     if (printDirNames) {
1355         printf("baseDir is [%s]\n", baseDir.c_str());
1356     }
1357
1358     if (!comparisonDir.endsWith(PATH_DIV_STR)) {
1359         comparisonDir.append(PATH_DIV_STR);
1360     }
1361     if (printDirNames) {
1362         printf("comparisonDir is [%s]\n", comparisonDir.c_str());
1363     }
1364
1365     if (!outputDir.endsWith(PATH_DIV_STR)) {
1366         outputDir.append(PATH_DIV_STR);
1367     }
1368     if (generateDiffs) {
1369         if (printDirNames) {
1370             printf("writing diffs to outputDir is [%s]\n", outputDir.c_str());
1371         }
1372     } else {
1373         if (printDirNames) {
1374             printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str());
1375         }
1376         outputDir.set("");
1377     }
1378
1379     // If no matchSubstrings were specified, match ALL strings
1380     // (except for whatever nomatchSubstrings were specified, if any).
1381     if (matchSubstrings.isEmpty()) {
1382         matchSubstrings.push(new SkString(""));
1383     }
1384
1385     create_diff_images(diffProc, colorThreshold, &differences,
1386                        baseDir, comparisonDir, outputDir,
1387                        matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, &summary);
1388     summary.print(listFilenames, failOnResultType);
1389
1390     if (differences.count()) {
1391         qsort(differences.begin(), differences.count(),
1392               sizeof(DiffRecord*), sortProc);
1393     }
1394
1395     if (generateDiffs) {
1396         print_diff_page(summary.fNumMatches, colorThreshold, differences,
1397                         baseDir, comparisonDir, outputDir);
1398     }
1399
1400     for (i = 0; i < differences.count(); i++) {
1401         delete differences[i];
1402     }
1403     matchSubstrings.deleteAll();
1404     nomatchSubstrings.deleteAll();
1405
1406     int num_failing_results = 0;
1407     for (int i = 0; i < kNumResultTypes; i++) {
1408         if (failOnResultType[i]) {
1409             num_failing_results += summary.fResultsOfType[i].count();
1410         }
1411     }
1412
1413     // On Linux (and maybe other platforms too), any results outside of the
1414     // range [0...255] are wrapped (mod 256).  Do the conversion ourselves, to
1415     // make sure that we only return 0 when there were no failures.
1416     return (num_failing_results > 255) ? 255 : num_failing_results;
1417 }