build iOS with 'make all'
[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 static 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 static 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     stream->writeText("select image</th>\n<th>");
935     if (doOutputDate) {
936         SkTime::DateTime dt;
937         SkTime::GetDateTime(&dt);
938         stream->writeText("SkDiff run at ");
939         stream->writeDecAsText(dt.fHour);
940         stream->writeText(":");
941         if (dt.fMinute < 10) {
942             stream->writeText("0");
943         }
944         stream->writeDecAsText(dt.fMinute);
945         stream->writeText(":");
946         if (dt.fSecond < 10) {
947             stream->writeText("0");
948         }
949         stream->writeDecAsText(dt.fSecond);
950         stream->writeText("<br>");
951     }
952     stream->writeDecAsText(matchCount);
953     stream->writeText(" of ");
954     stream->writeDecAsText(differences.count());
955     stream->writeText(" images matched ");
956     if (colorThreshold == 0) {
957         stream->writeText("exactly");
958     } else {
959         stream->writeText("within ");
960         stream->writeDecAsText(colorThreshold);
961         stream->writeText(" color units per component");
962     }
963     stream->writeText(".<br>");
964     stream->writeText("</th>\n<th>");
965     stream->writeText("every different pixel shown in white");
966     stream->writeText("</th>\n<th>");
967     stream->writeText("color difference at each pixel");
968     stream->writeText("</th>\n<th>baseDir: ");
969     stream->writeText(baseDir.c_str());
970     stream->writeText("</th>\n<th>comparisonDir: ");
971     stream->writeText(comparisonDir.c_str());
972     stream->writeText("</th>\n");
973     stream->writeText("</tr>\n");
974 }
975
976 static void print_pixel_count (SkFILEWStream* stream,
977                                const DiffRecord& diff) {
978     stream->writeText("<br>(");
979     stream->writeDecAsText(static_cast<int>(diff.fFractionDifference *
980                                             diff.fBaseWidth *
981                                             diff.fBaseHeight));
982     stream->writeText(" pixels)");
983 /*
984     stream->writeDecAsText(diff.fWeightedFraction *
985                            diff.fBaseWidth *
986                            diff.fBaseHeight);
987     stream->writeText(" weighted pixels)");
988 */
989 }
990
991 static void print_checkbox_cell (SkFILEWStream* stream,
992                                  const DiffRecord& diff) {
993     stream->writeText("<td><input type=\"checkbox\" name=\"");
994     stream->writeText(diff.fFilename.c_str());
995     stream->writeText("\" checked=\"yes\"></td>");
996 }
997
998 static void print_label_cell (SkFILEWStream* stream,
999                               const DiffRecord& diff) {
1000     char metricBuf [20];
1001
1002     stream->writeText("<td><b>");
1003     stream->writeText(diff.fFilename.c_str());
1004     stream->writeText("</b><br>");
1005     switch (diff.fResult) {
1006       case kEqualBits:
1007         SkDEBUGFAIL("should not encounter DiffRecord with kEqualBits here");
1008         return;
1009       case kEqualPixels:
1010         SkDEBUGFAIL("should not encounter DiffRecord with kEqualPixels here");
1011         return;
1012       case kDifferentSizes:
1013         stream->writeText("Image sizes differ</td>");
1014         return;
1015       case kDifferentPixels:
1016         sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
1017         stream->writeText(metricBuf);
1018         stream->writeText(" of pixels differ");
1019         stream->writeText("\n  (");
1020         sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
1021         stream->writeText(metricBuf);
1022         stream->writeText(" weighted)");
1023         // Write the actual number of pixels that differ if it's < 1%
1024         if (diff.fFractionDifference < 0.01) {
1025             print_pixel_count(stream, diff);
1026         }
1027         stream->writeText("<br>Average color mismatch ");
1028         stream->writeDecAsText(static_cast<int>(MAX3(diff.fAverageMismatchR,
1029                                                      diff.fAverageMismatchG,
1030                                                      diff.fAverageMismatchB)));
1031         stream->writeText("<br>Max color mismatch ");
1032         stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
1033                                     diff.fMaxMismatchG,
1034                                     diff.fMaxMismatchB));
1035         stream->writeText("</td>");
1036         break;
1037       case kDifferentOther:
1038         stream->writeText("Files differ; unable to parse one or both files</td>");
1039         return;
1040       case kBaseMissing:
1041         stream->writeText("Missing from baseDir</td>");
1042         return;
1043       case kComparisonMissing:
1044         stream->writeText("Missing from comparisonDir</td>");
1045         return;
1046       default:
1047         SkDEBUGFAIL("encountered DiffRecord with unknown result type");
1048         return;
1049     }
1050 }
1051
1052 static void print_image_cell (SkFILEWStream* stream,
1053                               const SkString& path,
1054                               int height) {
1055     stream->writeText("<td><a href=\"");
1056     stream->writeText(path.c_str());
1057     stream->writeText("\"><img src=\"");
1058     stream->writeText(path.c_str());
1059     stream->writeText("\" height=\"");
1060     stream->writeDecAsText(height);
1061     stream->writeText("px\"></a></td>");
1062 }
1063
1064 #if 0 // UNUSED
1065 static void print_text_cell (SkFILEWStream* stream, const char* text) {
1066     stream->writeText("<td align=center>");
1067     if (NULL != text) {
1068         stream->writeText(text);
1069     }
1070     stream->writeText("</td>");
1071 }
1072 #endif
1073
1074 static void print_diff_with_missing_file(SkFILEWStream* stream,
1075                                          DiffRecord& diff,
1076                                          const SkString& relativePath) {
1077     stream->writeText("<tr>\n");
1078     print_checkbox_cell(stream, diff);
1079     print_label_cell(stream, diff);
1080     stream->writeText("<td>N/A</td>");
1081     stream->writeText("<td>N/A</td>");
1082     if (kBaseMissing != diff.fResult) {
1083         int h, w;
1084         if (!get_bitmap_height_width(diff.fBasePath, &h, &w)) {
1085             stream->writeText("<td>N/A</td>");
1086         } else {
1087             int height = compute_image_height(h, w);
1088             if (!diff.fBasePath.startsWith(PATH_DIV_STR)) {
1089                 diff.fBasePath.prepend(relativePath);
1090             }
1091             print_image_cell(stream, diff.fBasePath, height);
1092         }
1093     } else {
1094         stream->writeText("<td>N/A</td>");
1095     }
1096     if (kComparisonMissing != diff.fResult) {
1097         int h, w;
1098         if (!get_bitmap_height_width(diff.fComparisonPath, &h, &w)) {
1099             stream->writeText("<td>N/A</td>");
1100         } else {
1101             int height = compute_image_height(h, w);
1102             if (!diff.fComparisonPath.startsWith(PATH_DIV_STR)) {
1103                 diff.fComparisonPath.prepend(relativePath);
1104             }
1105             print_image_cell(stream, diff.fComparisonPath, height);
1106         }
1107     } else {
1108         stream->writeText("<td>N/A</td>");
1109     }
1110     stream->writeText("</tr>\n");
1111     stream->flush();
1112 }
1113
1114 static void print_diff_page (const int matchCount,
1115                              const int colorThreshold,
1116                              const RecordArray& differences,
1117                              const SkString& baseDir,
1118                              const SkString& comparisonDir,
1119                              const SkString& outputDir) {
1120
1121     SkASSERT(!baseDir.isEmpty());
1122     SkASSERT(!comparisonDir.isEmpty());
1123     SkASSERT(!outputDir.isEmpty());
1124
1125     SkString outputPath (outputDir);
1126     outputPath.append("index.html");
1127     //SkFILEWStream outputStream ("index.html");
1128     SkFILEWStream outputStream (outputPath.c_str());
1129
1130     // Need to convert paths from relative-to-cwd to relative-to-outputDir
1131     // FIXME this doesn't work if there are '..' inside the outputDir
1132
1133     bool isPathAbsolute = false;
1134     // On Windows or Linux, a path starting with PATH_DIV_CHAR is absolute.
1135     if (outputDir.size() > 0 && PATH_DIV_CHAR == outputDir[0]) {
1136         isPathAbsolute = true;
1137     }
1138 #ifdef SK_BUILD_FOR_WIN32
1139     // On Windows, absolute paths can also start with "x:", where x is any
1140     // drive letter.
1141     if (outputDir.size() > 1 && ':' == outputDir[1]) {
1142         isPathAbsolute = true;
1143     }
1144 #endif
1145
1146     SkString relativePath;
1147     if (!isPathAbsolute) {
1148         unsigned int ui;
1149         for (ui = 0; ui < outputDir.size(); ui++) {
1150             if (outputDir[ui] == PATH_DIV_CHAR) {
1151                 relativePath.append(".." PATH_DIV_STR);
1152             }
1153         }
1154     }
1155
1156     outputStream.writeText(
1157         "<html>\n<head>\n"
1158         "<script src=\"https://ajax.googleapis.com/ajax/"
1159         "libs/jquery/1.7.2/jquery.min.js\"></script>\n"
1160         "<script type=\"text/javascript\">\n"
1161         "function generateCheckedList() {\n"
1162         "var boxes = $(\":checkbox:checked\");\n"
1163         "var fileCmdLineString = '';\n"
1164         "var fileMultiLineString = '';\n"
1165         "for (var i = 0; i < boxes.length; i++) {\n"
1166         "fileMultiLineString += boxes[i].name + '<br>';\n"
1167         "fileCmdLineString += boxes[i].name + '&nbsp;';\n"
1168         "}\n"
1169         "$(\"#checkedList\").html(fileCmdLineString + "
1170         "'<br><br>' + fileMultiLineString);\n"
1171         "}\n"
1172         "</script>\n</head>\n<body>\n");
1173     print_table_header(&outputStream, matchCount, colorThreshold, differences,
1174                        baseDir, comparisonDir);
1175     int i;
1176     for (i = 0; i < differences.count(); i++) {
1177         DiffRecord* diff = differences[i];
1178
1179         switch (diff->fResult) {
1180           // Cases in which there is no diff to report.
1181           case kEqualBits:
1182           case kEqualPixels:
1183             continue;
1184           // Cases in which we want a detailed pixel diff.
1185           case kDifferentPixels:
1186             break;
1187           // Cases in which the files differed, but we can't display the diff.
1188           case kDifferentSizes:
1189           case kDifferentOther:
1190           case kBaseMissing:
1191           case kComparisonMissing:
1192             print_diff_with_missing_file(&outputStream, *diff, relativePath);
1193             continue;
1194           default:
1195             SkDEBUGFAIL("encountered DiffRecord with unknown result type");
1196             continue;
1197         }
1198
1199         if (!diff->fBasePath.startsWith(PATH_DIV_STR)) {
1200             diff->fBasePath.prepend(relativePath);
1201         }
1202         if (!diff->fComparisonPath.startsWith(PATH_DIV_STR)) {
1203             diff->fComparisonPath.prepend(relativePath);
1204         }
1205
1206         int height = compute_image_height(diff->fBaseHeight, diff->fBaseWidth);
1207         outputStream.writeText("<tr>\n");
1208         print_checkbox_cell(&outputStream, *diff);
1209         print_label_cell(&outputStream, *diff);
1210         print_image_cell(&outputStream,
1211                          filename_to_white_filename(diff->fFilename), height);
1212         print_image_cell(&outputStream,
1213                          filename_to_diff_filename(diff->fFilename), height);
1214         print_image_cell(&outputStream, diff->fBasePath, height);
1215         print_image_cell(&outputStream, diff->fComparisonPath, height);
1216         outputStream.writeText("</tr>\n");
1217         outputStream.flush();
1218     }
1219     outputStream.writeText(
1220         "</table>\n"
1221         "<input type=\"button\" "
1222         "onclick=\"generateCheckedList()\" "
1223         "value=\"Create Rebaseline List\">\n"
1224         "<div id=\"checkedList\"></div>\n"
1225         "</body>\n</html>\n");
1226     outputStream.flush();
1227 }
1228
1229 static void usage (char * argv0) {
1230     SkDebugf("Skia baseline image diff tool\n");
1231     SkDebugf("\n"
1232 "Usage: \n"
1233 "    %s <baseDir> <comparisonDir> [outputDir] \n"
1234 , argv0, argv0);
1235     SkDebugf(
1236 "\nArguments:"
1237 "\n    --failonresult <result>: After comparing all file pairs, exit with nonzero"
1238 "\n                             return code (number of file pairs yielding this"
1239 "\n                             result) if any file pairs yielded this result."
1240 "\n                             This flag may be repeated, in which case the"
1241 "\n                             return code will be the number of fail pairs"
1242 "\n                             yielding ANY of these results."
1243 "\n    --help: display this info"
1244 "\n    --listfilenames: list all filenames for each result type in stdout"
1245 "\n    --match <substring>: compare files whose filenames contain this substring;"
1246 "\n                         if unspecified, compare ALL files."
1247 "\n                         this flag may be repeated."
1248 "\n    --nodiffs: don't write out image diffs or index.html, just generate"
1249 "\n               report on stdout"
1250 "\n    --nomatch <substring>: regardless of --match, DO NOT compare files whose"
1251 "\n                           filenames contain this substring."
1252 "\n                           this flag may be repeated."
1253 "\n    --noprintdirs: do not print the directories used."
1254 "\n    --norecurse: do not recurse into subdirectories."
1255 "\n    --sortbymaxmismatch: sort by worst color channel mismatch;"
1256 "\n                         break ties with -sortbymismatch"
1257 "\n    --sortbymismatch: sort by average color channel mismatch"
1258 "\n    --threshold <n>: only report differences > n (per color channel) [default 0]"
1259 "\n    --weighted: sort by # pixels different weighted by color difference"
1260 "\n"
1261 "\n    baseDir: directory to read baseline images from."
1262 "\n    comparisonDir: directory to read comparison images from"
1263 "\n    outputDir: directory to write difference images and index.html to;"
1264 "\n               defaults to comparisonDir"
1265 "\n"
1266 "\nIf no sort is specified, it will sort by fraction of pixels mismatching."
1267 "\n");
1268 }
1269
1270 const int kNoError = 0;
1271 const int kGenericError = -1;
1272
1273 int tool_main(int argc, char** argv);
1274 int tool_main(int argc, char** argv) {
1275     DiffMetricProc diffProc = compute_diff_pmcolor;
1276     int (*sortProc)(const void*, const void*) = compare<CompareDiffMetrics>;
1277
1278     // Maximum error tolerated in any one color channel in any one pixel before
1279     // a difference is reported.
1280     int colorThreshold = 0;
1281     SkString baseDir;
1282     SkString comparisonDir;
1283     SkString outputDir;
1284
1285     StringArray matchSubstrings;
1286     StringArray nomatchSubstrings;
1287
1288     bool generateDiffs = true;
1289     bool listFilenames = false;
1290     bool printDirNames = true;
1291     bool recurseIntoSubdirs = true;
1292
1293     RecordArray differences;
1294     DiffSummary summary;
1295
1296     bool failOnResultType[kNumResultTypes];
1297     for (int i = 0; i < kNumResultTypes; i++) {
1298         failOnResultType[i] = false;
1299     }
1300
1301     int i;
1302     int numUnflaggedArguments = 0;
1303     for (i = 1; i < argc; i++) {
1304         if (!strcmp(argv[i], "--failonresult")) {
1305             Result type = getResultByName(argv[++i]);
1306             failOnResultType[type] = true;
1307             continue;
1308         }
1309         if (!strcmp(argv[i], "--help")) {
1310             usage(argv[0]);
1311             return kNoError;
1312         }
1313         if (!strcmp(argv[i], "--listfilenames")) {
1314             listFilenames = true;
1315             continue;
1316         }
1317         if (!strcmp(argv[i], "--match")) {
1318             matchSubstrings.push(new SkString(argv[++i]));
1319             continue;
1320         }
1321         if (!strcmp(argv[i], "--nodiffs")) {
1322             generateDiffs = false;
1323             continue;
1324         }
1325         if (!strcmp(argv[i], "--nomatch")) {
1326             nomatchSubstrings.push(new SkString(argv[++i]));
1327             continue;
1328         }
1329         if (!strcmp(argv[i], "--noprintdirs")) {
1330             printDirNames = false;
1331             continue;
1332         }
1333         if (!strcmp(argv[i], "--norecurse")) {
1334             recurseIntoSubdirs = false;
1335             continue;
1336         }
1337         if (!strcmp(argv[i], "--sortbymaxmismatch")) {
1338             sortProc = compare<CompareDiffMaxMismatches>;
1339             continue;
1340         }
1341         if (!strcmp(argv[i], "--sortbymismatch")) {
1342             sortProc = compare<CompareDiffMeanMismatches>;
1343             continue;
1344         }
1345         if (!strcmp(argv[i], "--threshold")) {
1346             colorThreshold = atoi(argv[++i]);
1347             continue;
1348         }
1349         if (!strcmp(argv[i], "--weighted")) {
1350             sortProc = compare<CompareDiffWeighted>;
1351             continue;
1352         }
1353         if (argv[i][0] != '-') {
1354             switch (numUnflaggedArguments++) {
1355                 case 0:
1356                     baseDir.set(argv[i]);
1357                     continue;
1358                 case 1:
1359                     comparisonDir.set(argv[i]);
1360                     continue;
1361                 case 2:
1362                     outputDir.set(argv[i]);
1363                     continue;
1364                 default:
1365                     SkDebugf("extra unflagged argument <%s>\n", argv[i]);
1366                     usage(argv[0]);
1367                     return kGenericError;
1368             }
1369         }
1370
1371         SkDebugf("Unrecognized argument <%s>\n", argv[i]);
1372         usage(argv[0]);
1373         return kGenericError;
1374     }
1375
1376     if (numUnflaggedArguments == 2) {
1377         outputDir = comparisonDir;
1378     } else if (numUnflaggedArguments != 3) {
1379         usage(argv[0]);
1380         return kGenericError;
1381     }
1382
1383     if (!baseDir.endsWith(PATH_DIV_STR)) {
1384         baseDir.append(PATH_DIV_STR);
1385     }
1386     if (printDirNames) {
1387         printf("baseDir is [%s]\n", baseDir.c_str());
1388     }
1389
1390     if (!comparisonDir.endsWith(PATH_DIV_STR)) {
1391         comparisonDir.append(PATH_DIV_STR);
1392     }
1393     if (printDirNames) {
1394         printf("comparisonDir is [%s]\n", comparisonDir.c_str());
1395     }
1396
1397     if (!outputDir.endsWith(PATH_DIV_STR)) {
1398         outputDir.append(PATH_DIV_STR);
1399     }
1400     if (generateDiffs) {
1401         if (printDirNames) {
1402             printf("writing diffs to outputDir is [%s]\n", outputDir.c_str());
1403         }
1404     } else {
1405         if (printDirNames) {
1406             printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str());
1407         }
1408         outputDir.set("");
1409     }
1410
1411     // If no matchSubstrings were specified, match ALL strings
1412     // (except for whatever nomatchSubstrings were specified, if any).
1413     if (matchSubstrings.isEmpty()) {
1414         matchSubstrings.push(new SkString(""));
1415     }
1416
1417     create_diff_images(diffProc, colorThreshold, &differences,
1418                        baseDir, comparisonDir, outputDir,
1419                        matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, &summary);
1420     summary.print(listFilenames, failOnResultType);
1421
1422     if (differences.count()) {
1423         qsort(differences.begin(), differences.count(),
1424               sizeof(DiffRecord*), sortProc);
1425     }
1426
1427     if (generateDiffs) {
1428         print_diff_page(summary.fNumMatches, colorThreshold, differences,
1429                         baseDir, comparisonDir, outputDir);
1430     }
1431
1432     for (i = 0; i < differences.count(); i++) {
1433         delete differences[i];
1434     }
1435     matchSubstrings.deleteAll();
1436     nomatchSubstrings.deleteAll();
1437
1438     int num_failing_results = 0;
1439     for (int i = 0; i < kNumResultTypes; i++) {
1440         if (failOnResultType[i]) {
1441             num_failing_results += summary.fResultsOfType[i].count();
1442         }
1443     }
1444
1445     // On Linux (and maybe other platforms too), any results outside of the
1446     // range [0...255] are wrapped (mod 256).  Do the conversion ourselves, to
1447     // make sure that we only return 0 when there were no failures.
1448     return (num_failing_results > 255) ? 255 : num_failing_results;
1449 }
1450
1451 #if !defined SK_BUILD_FOR_IOS
1452 int main(int argc, char * const argv[]) {
1453     return tool_main(argc, (char**) argv);
1454 }
1455 #endif