skdiff: no longer skip .pdf files by default
[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  * Does *not* recursively descend directories.
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 enum Result {
44     kEqualBits,      // both files in the pair contain exactly the same bits
45     kEqualPixels,    // not bitwise equal, but their pixels are exactly the same
46     kDifferentSizes, // both are images we can parse, but of different sizes
47     kDifferentPixels,// both are images we can parse, but with different pixels
48     kDifferentOther, // files have different bits but are not parsable images
49     kBaseMissing,      // missing from baseDir
50     kComparisonMissing,// missing from comparisonDir
51     kUnknown,        // not compared yet
52     //
53     kNumResultTypes  // NOT A VALID VALUE--used to set up arrays. Must be last.
54 };
55
56 struct DiffRecord {
57     DiffRecord (const SkString filename,
58                 const SkString basePath,
59                 const SkString comparisonPath,
60                 const Result result = kUnknown)
61         : fFilename (filename)
62         , fBasePath (basePath)
63         , fComparisonPath (comparisonPath)
64         , fBaseBitmap (new SkBitmap ())
65         , fComparisonBitmap (new SkBitmap ())
66         , fDifferenceBitmap (new SkBitmap ())
67         , fWhiteBitmap (new SkBitmap ())
68         , fBaseHeight (0)
69         , fBaseWidth (0)
70         , fFractionDifference (0)
71         , fWeightedFraction (0)
72         , fAverageMismatchR (0)
73         , fAverageMismatchG (0)
74         , fAverageMismatchB (0)
75         , fMaxMismatchR (0)
76         , fMaxMismatchG (0)
77         , fMaxMismatchB (0)
78         , fResult(result) {
79     };
80
81     SkString fFilename;
82     SkString fBasePath;
83     SkString fComparisonPath;
84
85     SkBitmap* fBaseBitmap;
86     SkBitmap* fComparisonBitmap;
87     SkBitmap* fDifferenceBitmap;
88     SkBitmap* fWhiteBitmap;
89
90     int fBaseHeight;
91     int fBaseWidth;
92
93     /// Arbitrary floating-point metric to be used to sort images from most
94     /// to least different from baseline; values of 0 will be omitted from the
95     /// summary webpage.
96     float fFractionDifference;
97     float fWeightedFraction;
98
99     float fAverageMismatchR;
100     float fAverageMismatchG;
101     float fAverageMismatchB;
102
103     uint32_t fMaxMismatchR;
104     uint32_t fMaxMismatchG;
105     uint32_t fMaxMismatchB;
106
107     /// Which category of diff result.
108     Result fResult;
109 };
110
111 #define MAX2(a,b) (((b) < (a)) ? (a) : (b))
112 #define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
113
114 const SkPMColor PMCOLOR_WHITE = SkPreMultiplyColor(SK_ColorWHITE);
115 const SkPMColor PMCOLOR_BLACK = SkPreMultiplyColor(SK_ColorBLACK);
116
117 typedef SkTDArray<SkString*> StringArray;
118 typedef StringArray FileArray;
119
120 struct DiffSummary {
121     DiffSummary ()
122         : fNumMatches (0)
123         , fNumMismatches (0)
124         , fMaxMismatchV (0)
125         , fMaxMismatchPercent (0) { };
126
127     ~DiffSummary() {
128         for (int i = 0; i < kNumResultTypes; i++) {
129             fResultsOfType[i].deleteAll();
130         }
131     }
132
133     uint32_t fNumMatches;
134     uint32_t fNumMismatches;
135     uint32_t fMaxMismatchV;
136     float fMaxMismatchPercent;
137
138     FileArray fResultsOfType[kNumResultTypes];
139
140     // Print the contents of this FileArray, if any, to stdout.
141     // (If the FileArray is empty, print nothing.)
142     void printContents(const FileArray& fileArray, const char* headerText) {
143         int n = fileArray.count();
144         if (n > 0) {
145             printf("%s:\n", headerText);
146             for (int i = 0; i < n; ++i) {
147                 printf("\t%s\n", fileArray[i]->c_str());
148             }
149         }
150     }
151
152     void print () {
153         printContents(fResultsOfType[kBaseMissing],
154                       "Missing in baseDir");
155         printContents(fResultsOfType[kComparisonMissing],
156                       "Missing in comparisonDir");
157         printf("%d of %d images matched.\n", fNumMatches,
158                fNumMatches + fNumMismatches);
159         if (fNumMismatches > 0) {
160             printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
161             printf("Largest area mismatch was %.2f%% of pixels\n",
162                    fMaxMismatchPercent);
163         }
164
165     }
166
167     void add (DiffRecord* drp) {
168         uint32_t mismatchValue;
169
170         fResultsOfType[drp->fResult].push(new SkString(drp->fFilename));
171         switch (drp->fResult) {
172           case kEqualBits:
173             fNumMatches++;
174             break;
175           case kEqualPixels:
176             fNumMatches++;
177             break;
178           case kDifferentSizes:
179             fNumMismatches++;
180             drp->fFractionDifference = 2;// sort as if 200% of pixels differed
181             break;
182           case kDifferentPixels:
183             fNumMismatches++;
184             if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
185                 fMaxMismatchPercent = drp->fFractionDifference * 100;
186             }
187             mismatchValue = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
188                                  drp->fMaxMismatchB);
189             if (mismatchValue > fMaxMismatchV) {
190                 fMaxMismatchV = mismatchValue;
191             }
192             break;
193           case kDifferentOther:
194             fNumMismatches++;
195             drp->fFractionDifference = 3;// sort as if 300% of pixels differed
196             break;
197           case kBaseMissing:
198             fNumMismatches++;
199             break;
200           case kComparisonMissing:
201             fNumMismatches++;
202             break;
203           case kUnknown:
204             SkDEBUGFAIL("adding uncategorized DiffRecord");
205             break;
206           default:
207             SkDEBUGFAIL("adding DiffRecord with unhandled fResult value");
208             break;
209         }
210     }
211 };
212
213 typedef SkTDArray<DiffRecord*> RecordArray;
214
215 /// Comparison routine for qsort;  sorts by fFractionDifference
216 /// from largest to smallest.
217 static int compare_diff_metrics (DiffRecord** lhs, DiffRecord** rhs) {
218     if ((*lhs)->fFractionDifference < (*rhs)->fFractionDifference) {
219         return 1;
220     }
221     if ((*rhs)->fFractionDifference < (*lhs)->fFractionDifference) {
222         return -1;
223     }
224     return 0;
225 }
226
227 static int compare_diff_weighted (DiffRecord** lhs, DiffRecord** rhs) {
228     if ((*lhs)->fWeightedFraction < (*rhs)->fWeightedFraction) {
229         return 1;
230     }
231     if ((*lhs)->fWeightedFraction > (*rhs)->fWeightedFraction) {
232         return -1;
233     }
234     return 0;
235 }
236
237 /// Comparison routine for qsort;  sorts by max(fAverageMismatch{RGB})
238 /// from largest to smallest.
239 static int compare_diff_mean_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
240     float leftValue = MAX3((*lhs)->fAverageMismatchR,
241                            (*lhs)->fAverageMismatchG,
242                            (*lhs)->fAverageMismatchB);
243     float rightValue = MAX3((*rhs)->fAverageMismatchR,
244                             (*rhs)->fAverageMismatchG,
245                             (*rhs)->fAverageMismatchB);
246     if (leftValue < rightValue) {
247         return 1;
248     }
249     if (rightValue < leftValue) {
250         return -1;
251     }
252     return 0;
253 }
254
255 /// Comparison routine for qsort;  sorts by max(fMaxMismatch{RGB})
256 /// from largest to smallest.
257 static int compare_diff_max_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
258     uint32_t leftValue = MAX3((*lhs)->fMaxMismatchR,
259                               (*lhs)->fMaxMismatchG,
260                               (*lhs)->fMaxMismatchB);
261     uint32_t rightValue = MAX3((*rhs)->fMaxMismatchR,
262                                (*rhs)->fMaxMismatchG,
263                                (*rhs)->fMaxMismatchB);
264     if (leftValue < rightValue) {
265         return 1;
266     }
267     if (rightValue < leftValue) {
268         return -1;
269     }
270     return compare_diff_mean_mismatches(lhs, rhs);
271 }
272
273
274
275 /// Parameterized routine to compute the color of a pixel in a difference image.
276 typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
277
278 #if 0 // UNUSED
279 static void expand_and_copy (int width, int height, SkBitmap** dest) {
280     SkBitmap* temp = new SkBitmap ();
281     temp->reset();
282     temp->setConfig((*dest)->config(), width, height);
283     temp->allocPixels();
284     (*dest)->copyPixelsTo(temp->getPixels(), temp->getSize(),
285                           temp->rowBytes());
286     *dest = temp;
287 }
288 #endif
289
290 /// Returns true if the two buffers passed in are both non-NULL, and include
291 /// exactly the same byte values (and identical lengths).
292 static bool are_buffers_equal(SkData* skdata1, SkData* skdata2) {
293     if ((NULL == skdata1) || (NULL == skdata2)) {
294         return false;
295     }
296     if (skdata1->size() != skdata2->size()) {
297         return false;
298     }
299     return (0 == memcmp(skdata1->data(), skdata2->data(), skdata1->size()));
300 }
301
302 /// Reads the file at the given path and returns its complete contents as an
303 /// SkData object (or returns NULL on error).
304 static SkData* read_file(const char* file_path) {
305     SkFILEStream fileStream(file_path);
306     if (!fileStream.isValid()) {
307         SkDebugf("WARNING: could not open file <%s> for reading\n", file_path);
308         return NULL;
309     }
310     size_t bytesInFile = fileStream.getLength();
311     size_t bytesLeftToRead = bytesInFile;
312
313     void* bufferStart = sk_malloc_throw(bytesInFile);
314     char* bufferPointer = (char*)bufferStart;
315     while (bytesLeftToRead > 0) {
316         size_t bytesReadThisTime = fileStream.read(
317             bufferPointer, bytesLeftToRead);
318         if (0 == bytesReadThisTime) {
319             SkDebugf("WARNING: error reading from <%s>\n", file_path);
320             sk_free(bufferStart);
321             return NULL;
322         }
323         bytesLeftToRead -= bytesReadThisTime;
324         bufferPointer += bytesReadThisTime;
325     }
326     return SkData::NewFromMalloc(bufferStart, bytesInFile);
327 }
328
329 /// Decodes binary contents of baseFile and comparisonFile into
330 /// diffRecord->fBaseBitmap and diffRecord->fComparisonBitmap.
331 /// Returns true if that succeeds.
332 static bool get_bitmaps (SkData* baseFileContents,
333                          SkData* comparisonFileContents,
334                          DiffRecord* diffRecord) {
335     SkMemoryStream compareStream(comparisonFileContents->data(),
336                                  comparisonFileContents->size());
337     SkMemoryStream baseStream(baseFileContents->data(),
338                               baseFileContents->size());
339
340     SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
341     if (NULL == codec) {
342         SkDebugf("ERROR: no codec found for basePath <%s>\n",
343                  diffRecord->fBasePath.c_str());
344         return false;
345     }
346
347     // In debug, the DLL will automatically be unloaded when this is deleted,
348     // but that shouldn't be a problem in release mode.
349     SkAutoTDelete<SkImageDecoder> ad(codec);
350
351     baseStream.rewind();
352     if (!codec->decode(&baseStream, diffRecord->fBaseBitmap,
353                        SkBitmap::kARGB_8888_Config,
354                        SkImageDecoder::kDecodePixels_Mode)) {
355         SkDebugf("ERROR: codec failed for basePath <%s>\n",
356                  diffRecord->fBasePath.c_str());
357         return false;
358     }
359
360     diffRecord->fBaseWidth = diffRecord->fBaseBitmap->width();
361     diffRecord->fBaseHeight = diffRecord->fBaseBitmap->height();
362
363     if (!codec->decode(&compareStream, diffRecord->fComparisonBitmap,
364                        SkBitmap::kARGB_8888_Config,
365                        SkImageDecoder::kDecodePixels_Mode)) {
366         SkDebugf("ERROR: codec failed for comparisonPath <%s>\n",
367                  diffRecord->fComparisonPath.c_str());
368         return false;
369     }
370
371     return true;
372 }
373
374 static bool get_bitmap_height_width(const SkString& path,
375                                     int *height, int *width) {
376     SkFILEStream stream(path.c_str());
377     if (!stream.isValid()) {
378         SkDebugf("ERROR: couldn't open file <%s>\n",
379                  path.c_str());
380         return false;
381     }
382
383     SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
384     if (NULL == codec) {
385         SkDebugf("ERROR: no codec found for <%s>\n",
386                  path.c_str());
387         return false;
388     }
389
390     SkAutoTDelete<SkImageDecoder> ad(codec);
391     SkBitmap bm;
392
393     stream.rewind();
394     if (!codec->decode(&stream, &bm,
395                        SkBitmap::kARGB_8888_Config,
396                        SkImageDecoder::kDecodePixels_Mode)) {
397         SkDebugf("ERROR: codec failed for <%s>\n",
398                  path.c_str());
399         return false;
400     }
401
402     *height = bm.height();
403     *width = bm.width();
404
405     return true;
406 }
407
408 // from gm - thanks to PNG, we need to force all pixels 100% opaque
409 static void force_all_opaque(const SkBitmap& bitmap) {
410    SkAutoLockPixels lock(bitmap);
411    for (int y = 0; y < bitmap.height(); y++) {
412        for (int x = 0; x < bitmap.width(); x++) {
413            *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
414        }
415    }
416 }
417
418 // from gm
419 static bool write_bitmap(const SkString& path, const SkBitmap* bitmap) {
420     SkBitmap copy;
421     bitmap->copyTo(&copy, SkBitmap::kARGB_8888_Config);
422     force_all_opaque(copy);
423     return SkImageEncoder::EncodeFile(path.c_str(), copy,
424                                       SkImageEncoder::kPNG_Type, 100);
425 }
426
427 // from gm
428 static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
429     int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
430     int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
431     int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
432
433     return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
434 }
435
436 static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1,
437                                             const int threshold) {
438     int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
439     int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
440     int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
441     int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
442
443     return ((SkAbs32(da) <= threshold) &&
444             (SkAbs32(dr) <= threshold) &&
445             (SkAbs32(dg) <= threshold) &&
446             (SkAbs32(db) <= threshold));
447 }
448
449 // based on gm
450 // Postcondition: when we exit this method, dr->fResult should have some value
451 // other than kUnknown.
452 static void compute_diff(DiffRecord* dr,
453                          DiffMetricProc diffFunction,
454                          const int colorThreshold) {
455     SkAutoLockPixels alpDiff(*dr->fDifferenceBitmap);
456     SkAutoLockPixels alpWhite(*dr->fWhiteBitmap);
457
458     const int w = dr->fComparisonBitmap->width();
459     const int h = dr->fComparisonBitmap->height();
460     int mismatchedPixels = 0;
461     int totalMismatchR = 0;
462     int totalMismatchG = 0;
463     int totalMismatchB = 0;
464
465     if (w != dr->fBaseWidth || h != dr->fBaseHeight) {
466         dr->fResult = kDifferentSizes;
467         return;
468     }
469     // Accumulate fractionally different pixels, then divide out
470     // # of pixels at the end.
471     dr->fWeightedFraction = 0;
472     for (int y = 0; y < h; y++) {
473         for (int x = 0; x < w; x++) {
474             SkPMColor c0 = *dr->fBaseBitmap->getAddr32(x, y);
475             SkPMColor c1 = *dr->fComparisonBitmap->getAddr32(x, y);
476             SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
477             SkPMColor outputDifference = diffFunction(c0, c1);
478             uint32_t thisR = SkGetPackedR32(trueDifference);
479             uint32_t thisG = SkGetPackedG32(trueDifference);
480             uint32_t thisB = SkGetPackedB32(trueDifference);
481             totalMismatchR += thisR;
482             totalMismatchG += thisG;
483             totalMismatchB += thisB;
484             // In HSV, value is defined as max RGB component.
485             int value = MAX3(thisR, thisG, thisB);
486             dr->fWeightedFraction += ((float) value) / 255;
487             if (thisR > dr->fMaxMismatchR) {
488                 dr->fMaxMismatchR = thisR;
489             }
490             if (thisG > dr->fMaxMismatchG) {
491                 dr->fMaxMismatchG = thisG;
492             }
493             if (thisB > dr->fMaxMismatchB) {
494                 dr->fMaxMismatchB = thisB;
495             }
496             if (!colors_match_thresholded(c0, c1, colorThreshold)) {
497                 mismatchedPixels++;
498                 *dr->fDifferenceBitmap->getAddr32(x, y) = outputDifference;
499                 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_WHITE;
500             } else {
501                 *dr->fDifferenceBitmap->getAddr32(x, y) = 0;
502                 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_BLACK;
503             }
504         }
505     }
506     if (0 == mismatchedPixels) {
507         dr->fResult = kEqualPixels;
508         return;
509     }
510     dr->fResult = kDifferentPixels;
511     int pixelCount = w * h;
512     dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
513     dr->fWeightedFraction /= pixelCount;
514     dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
515     dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
516     dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
517 }
518
519 static SkString filename_to_derived_filename (const SkString& filename,
520                                               const char *suffix) {
521     SkString diffName (filename);
522     const char* cstring = diffName.c_str();
523     int dotOffset = strrchr(cstring, '.') - cstring;
524     diffName.remove(dotOffset, diffName.size() - dotOffset);
525     diffName.append(suffix);
526     return diffName;
527 }
528
529 /// Given a image filename, returns the name of the file containing the
530 /// associated difference image.
531 static SkString filename_to_diff_filename (const SkString& filename) {
532     return filename_to_derived_filename(filename, "-diff.png");
533 }
534
535 /// Given a image filename, returns the name of the file containing the
536 /// "white" difference image.
537 static SkString filename_to_white_filename (const SkString& filename) {
538     return filename_to_derived_filename(filename, "-white.png");
539 }
540
541 static void release_bitmaps(DiffRecord* drp) {
542     delete drp->fBaseBitmap;
543     drp->fBaseBitmap = NULL;
544     delete drp->fComparisonBitmap;
545     drp->fComparisonBitmap = NULL;
546     delete drp->fDifferenceBitmap;
547     drp->fDifferenceBitmap = NULL;
548     delete drp->fWhiteBitmap;
549     drp->fWhiteBitmap = NULL;
550 }
551
552
553 /// If outputDir.isEmpty(), don't write out diff files.
554 static void create_and_write_diff_image(DiffRecord* drp,
555                                         DiffMetricProc dmp,
556                                         const int colorThreshold,
557                                         const SkString& outputDir,
558                                         const SkString& filename) {
559     const int w = drp->fBaseWidth;
560     const int h = drp->fBaseHeight;
561     drp->fDifferenceBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
562     drp->fDifferenceBitmap->allocPixels();
563     drp->fWhiteBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
564     drp->fWhiteBitmap->allocPixels();
565
566     SkASSERT(kUnknown == drp->fResult);
567     compute_diff(drp, dmp, colorThreshold);
568     SkASSERT(kUnknown != drp->fResult);
569
570     if ((kDifferentPixels == drp->fResult) && !outputDir.isEmpty()) {
571         SkString differencePath (outputDir);
572         differencePath.append(filename_to_diff_filename(filename));
573         write_bitmap(differencePath, drp->fDifferenceBitmap);
574         SkString whitePath (outputDir);
575         whitePath.append(filename_to_white_filename(filename));
576         write_bitmap(whitePath, drp->fWhiteBitmap);
577     }
578
579     release_bitmaps(drp);
580 }
581
582 /// Returns true if string contains any of these substrings.
583 static bool string_contains_any_of(const SkString& string,
584                                    const StringArray& substrings) {
585     for (int i = 0; i < substrings.count(); i++) {
586         if (string.contains(substrings[i]->c_str())) {
587             return true;
588         }
589     }
590     return false;
591 }
592
593 /// Iterate over dir and get all files that:
594 ///  - match any of the substrings in matchSubstrings, but...
595 ///  - DO NOT match any of the substrings in nomatchSubstrings
596 /// Returns the list of files in *files.
597 static void get_file_list(const SkString& dir,
598                           const StringArray& matchSubstrings,
599                           const StringArray& nomatchSubstrings,
600                           FileArray *files) {
601     SkOSFile::Iter it(dir.c_str());
602     SkString filename;
603     while (it.next(&filename)) {
604         if (string_contains_any_of(filename, matchSubstrings) &&
605             !string_contains_any_of(filename, nomatchSubstrings)) {
606             files->push(new SkString(filename));
607         }
608     }
609 }
610
611 static void release_file_list(FileArray *files) {
612     files->deleteAll();
613 }
614
615 /// Comparison routines for qsort, sort by file names.
616 static int compare_file_name_metrics(SkString **lhs, SkString **rhs) {
617     return strcmp((*lhs)->c_str(), (*rhs)->c_str());
618 }
619
620 /// Creates difference images, returns the number that have a 0 metric.
621 /// If outputDir.isEmpty(), don't write out diff files.
622 static void create_diff_images (DiffMetricProc dmp,
623                                 const int colorThreshold,
624                                 RecordArray* differences,
625                                 const SkString& baseDir,
626                                 const SkString& comparisonDir,
627                                 const SkString& outputDir,
628                                 const StringArray& matchSubstrings,
629                                 const StringArray& nomatchSubstrings,
630                                 DiffSummary* summary) {
631     SkASSERT(!baseDir.isEmpty());
632     SkASSERT(!comparisonDir.isEmpty());
633
634     FileArray baseFiles;
635     FileArray comparisonFiles;
636
637     get_file_list(baseDir, matchSubstrings, nomatchSubstrings, &baseFiles);
638     get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings,
639                   &comparisonFiles);
640
641     if (!baseFiles.isEmpty()) {
642         qsort(baseFiles.begin(), baseFiles.count(), sizeof(SkString*),
643               SkCastForQSort(compare_file_name_metrics));
644     }
645     if (!comparisonFiles.isEmpty()) {
646         qsort(comparisonFiles.begin(), comparisonFiles.count(),
647               sizeof(SkString*), SkCastForQSort(compare_file_name_metrics));
648     }
649
650     int i = 0;
651     int j = 0;
652
653     while (i < baseFiles.count() &&
654            j < comparisonFiles.count()) {
655
656         SkString basePath (baseDir);
657         basePath.append(*baseFiles[i]);
658         SkString comparisonPath (comparisonDir);
659         comparisonPath.append(*comparisonFiles[j]);
660
661         DiffRecord *drp = NULL;
662         int v = strcmp(baseFiles[i]->c_str(),
663                        comparisonFiles[j]->c_str());
664
665         if (v < 0) {
666             // in baseDir, but not in comparisonDir
667             drp = new DiffRecord(*baseFiles[i], basePath, comparisonPath,
668                                  kComparisonMissing);
669             ++i;
670         } else if (v > 0) {
671             // in comparisonDir, but not in baseDir
672             drp = new DiffRecord(*comparisonFiles[j], basePath, comparisonPath,
673                                  kBaseMissing);
674             ++j;
675         } else {
676             // Found the same filename in both baseDir and comparisonDir.
677             drp = new DiffRecord(*baseFiles[i], basePath, comparisonPath);
678             SkASSERT(kUnknown == drp->fResult);
679
680             SkData* baseFileBits;
681             SkData* comparisonFileBits;
682             if (NULL == (baseFileBits = read_file(basePath.c_str()))) {
683                 SkDebugf("WARNING: couldn't read base file <%s>\n",
684                          basePath.c_str());
685                 drp->fResult = kBaseMissing;
686             } else if (NULL == (comparisonFileBits = read_file(
687                 comparisonPath.c_str()))) {
688                 SkDebugf("WARNING: couldn't read comparison file <%s>\n",
689                          comparisonPath.c_str());
690                 drp->fResult = kComparisonMissing;
691             } else {
692                 if (are_buffers_equal(baseFileBits, comparisonFileBits)) {
693                     drp->fResult = kEqualBits;
694                 } else if (get_bitmaps(baseFileBits, comparisonFileBits, drp)) {
695                     create_and_write_diff_image(drp, dmp, colorThreshold,
696                                                 outputDir, *baseFiles[i]);
697                 } else {
698                     drp->fResult = kDifferentOther;
699                 }
700             }
701             if (baseFileBits) {
702                 baseFileBits->unref();
703             }
704             if (comparisonFileBits) {
705                 comparisonFileBits->unref();
706             }
707             ++i;
708             ++j;
709         }
710         SkASSERT(kUnknown != drp->fResult);
711         differences->push(drp);
712         summary->add(drp);
713     }
714
715     for (; i < baseFiles.count(); ++i) {
716         // files only in baseDir
717         SkString basePath (baseDir);
718         basePath.append(*baseFiles[i]);
719         SkString comparisonPath;
720         DiffRecord *drp = new DiffRecord(*baseFiles[i], basePath,
721                                          comparisonPath, kComparisonMissing);
722         differences->push(drp);
723         summary->add(drp);
724     }
725
726     for (; j < comparisonFiles.count(); ++j) {
727         // files only in comparisonDir
728         SkString basePath;
729         SkString comparisonPath(comparisonDir);
730         comparisonPath.append(*comparisonFiles[j]);
731         DiffRecord *drp = new DiffRecord(*comparisonFiles[j], basePath,
732                                          comparisonPath, kBaseMissing);
733         differences->push(drp);
734         summary->add(drp);
735     }
736
737     release_file_list(&baseFiles);
738     release_file_list(&comparisonFiles);
739 }
740
741 /// Make layout more consistent by scaling image to 240 height, 360 width,
742 /// or natural size, whichever is smallest.
743 static int compute_image_height (int height, int width) {
744     int retval = 240;
745     if (height < retval) {
746         retval = height;
747     }
748     float scale = (float) retval / height;
749     if (width * scale > 360) {
750         scale = (float) 360 / width;
751         retval = static_cast<int>(height * scale);
752     }
753     return retval;
754 }
755
756 static void print_table_header (SkFILEWStream* stream,
757                                 const int matchCount,
758                                 const int colorThreshold,
759                                 const RecordArray& differences,
760                                 const SkString &baseDir,
761                                 const SkString &comparisonDir,
762                                 bool doOutputDate=false) {
763     stream->writeText("<table>\n");
764     stream->writeText("<tr><th>");
765     if (doOutputDate) {
766         SkTime::DateTime dt;
767         SkTime::GetDateTime(&dt);
768         stream->writeText("SkDiff run at ");
769         stream->writeDecAsText(dt.fHour);
770         stream->writeText(":");
771         if (dt.fMinute < 10) {
772             stream->writeText("0");
773         }
774         stream->writeDecAsText(dt.fMinute);
775         stream->writeText(":");
776         if (dt.fSecond < 10) {
777             stream->writeText("0");
778         }
779         stream->writeDecAsText(dt.fSecond);
780         stream->writeText("<br>");
781     }
782     stream->writeDecAsText(matchCount);
783     stream->writeText(" of ");
784     stream->writeDecAsText(differences.count());
785     stream->writeText(" images matched ");
786     if (colorThreshold == 0) {
787         stream->writeText("exactly");
788     } else {
789         stream->writeText("within ");
790         stream->writeDecAsText(colorThreshold);
791         stream->writeText(" color units per component");
792     }
793     stream->writeText(".<br>");
794     stream->writeText("</th>\n<th>");
795     stream->writeText("every different pixel shown in white");
796     stream->writeText("</th>\n<th>");
797     stream->writeText("color difference at each pixel");
798     stream->writeText("</th>\n<th>");
799     stream->writeText(baseDir.c_str());
800     stream->writeText("</th>\n<th>");
801     stream->writeText(comparisonDir.c_str());
802     stream->writeText("</th>\n");
803     stream->writeText("</tr>\n");
804 }
805
806 static void print_pixel_count (SkFILEWStream* stream,
807                                const DiffRecord& diff) {
808     stream->writeText("<br>(");
809     stream->writeDecAsText(static_cast<int>(diff.fFractionDifference *
810                                             diff.fBaseWidth *
811                                             diff.fBaseHeight));
812     stream->writeText(" pixels)");
813 /*
814     stream->writeDecAsText(diff.fWeightedFraction *
815                            diff.fBaseWidth *
816                            diff.fBaseHeight);
817     stream->writeText(" weighted pixels)");
818 */
819 }
820
821 static void print_label_cell (SkFILEWStream* stream,
822                               const DiffRecord& diff) {
823     char metricBuf [20];
824
825     stream->writeText("<td><b>");
826     stream->writeText(diff.fFilename.c_str());
827     stream->writeText("</b><br>");
828     switch (diff.fResult) {
829       case kEqualBits:
830         SkDEBUGFAIL("should not encounter DiffRecord with kEqualBits here");
831         return;
832       case kEqualPixels:
833         SkDEBUGFAIL("should not encounter DiffRecord with kEqualPixels here");
834         return;
835       case kDifferentSizes:
836         stream->writeText("Image sizes differ</td>");
837         return;
838       case kDifferentPixels:
839         sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
840         stream->writeText(metricBuf);
841         stream->writeText(" of pixels differ");
842         stream->writeText("\n  (");
843         sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
844         stream->writeText(metricBuf);
845         stream->writeText(" weighted)");
846         // Write the actual number of pixels that differ if it's < 1%
847         if (diff.fFractionDifference < 0.01) {
848             print_pixel_count(stream, diff);
849         }
850         stream->writeText("<br>Average color mismatch ");
851         stream->writeDecAsText(static_cast<int>(MAX3(diff.fAverageMismatchR,
852                                                      diff.fAverageMismatchG,
853                                                      diff.fAverageMismatchB)));
854         stream->writeText("<br>Max color mismatch ");
855         stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
856                                     diff.fMaxMismatchG,
857                                     diff.fMaxMismatchB));
858         stream->writeText("</td>");
859         break;
860       case kDifferentOther:
861         stream->writeText("Files differ; unable to parse one or both files</td>");
862         return;
863       case kBaseMissing:
864         stream->writeText("Missing from baseDir</td>");
865         return;
866       case kComparisonMissing:
867         stream->writeText("Missing from comparisonDir</td>");
868         return;
869       default:
870         SkDEBUGFAIL("encountered DiffRecord with unknown result type");
871         return;
872     }
873 }
874
875 static void print_image_cell (SkFILEWStream* stream,
876                               const SkString& path,
877                               int height) {
878     stream->writeText("<td><a href=\"");
879     stream->writeText(path.c_str());
880     stream->writeText("\"><img src=\"");
881     stream->writeText(path.c_str());
882     stream->writeText("\" height=\"");
883     stream->writeDecAsText(height);
884     stream->writeText("px\"></a></td>");
885 }
886
887 #if 0 // UNUSED
888 static void print_text_cell (SkFILEWStream* stream, const char* text) {
889     stream->writeText("<td align=center>");
890     if (NULL != text) {
891         stream->writeText(text);
892     }
893     stream->writeText("</td>");
894 }
895 #endif
896
897 static void print_diff_with_missing_file(SkFILEWStream* stream,
898                                          DiffRecord& diff,
899                                          const SkString& relativePath) {
900     stream->writeText("<tr>\n");
901     print_label_cell(stream, diff);
902     stream->writeText("<td>N/A</td>");
903     stream->writeText("<td>N/A</td>");
904     if (kBaseMissing != diff.fResult) {
905         int h, w;
906         if (!get_bitmap_height_width(diff.fBasePath, &h, &w)) {
907             stream->writeText("<td>N/A</td>");
908         } else {
909             int height = compute_image_height(h, w);
910             if (!diff.fBasePath.startsWith(PATH_DIV_STR)) {
911                 diff.fBasePath.prepend(relativePath);
912             }
913             print_image_cell(stream, diff.fBasePath, height);
914         }
915     } else {
916         stream->writeText("<td>N/A</td>");
917     }
918     if (kComparisonMissing != diff.fResult) {
919         int h, w;
920         if (!get_bitmap_height_width(diff.fComparisonPath, &h, &w)) {
921             stream->writeText("<td>N/A</td>");
922         } else {
923             int height = compute_image_height(h, w);
924             if (!diff.fComparisonPath.startsWith(PATH_DIV_STR)) {
925                 diff.fComparisonPath.prepend(relativePath);
926             }
927             print_image_cell(stream, diff.fComparisonPath, height);
928         }
929     } else {
930         stream->writeText("<td>N/A</td>");
931     }
932     stream->writeText("</tr>\n");
933     stream->flush();
934 }
935
936 static void print_diff_page (const int matchCount,
937                              const int colorThreshold,
938                              const RecordArray& differences,
939                              const SkString& baseDir,
940                              const SkString& comparisonDir,
941                              const SkString& outputDir) {
942
943     SkASSERT(!baseDir.isEmpty());
944     SkASSERT(!comparisonDir.isEmpty());
945     SkASSERT(!outputDir.isEmpty());
946
947     SkString outputPath (outputDir);
948     outputPath.append("index.html");
949     //SkFILEWStream outputStream ("index.html");
950     SkFILEWStream outputStream (outputPath.c_str());
951
952     // Need to convert paths from relative-to-cwd to relative-to-outputDir
953     // FIXME this doesn't work if there are '..' inside the outputDir
954     unsigned int ui;
955     SkString relativePath;
956     for (ui = 0; ui < outputDir.size(); ui++) {
957         if (outputDir[ui] == PATH_DIV_CHAR) {
958             relativePath.append(".." PATH_DIV_STR);
959         }
960     }
961
962     outputStream.writeText("<html>\n<body>\n");
963     print_table_header(&outputStream, matchCount, colorThreshold, differences,
964                        baseDir, comparisonDir);
965     int i;
966     for (i = 0; i < differences.count(); i++) {
967         DiffRecord* diff = differences[i];
968
969         switch (diff->fResult) {
970           // Cases in which there is no diff to report.
971           case kEqualBits:
972           case kEqualPixels:
973             continue;
974           // Cases in which we want a detailed pixel diff.
975           case kDifferentPixels:
976             break;
977           // Cases in which the files differed, but we can't display the diff.
978           case kDifferentSizes:
979           case kDifferentOther:
980           case kBaseMissing:
981           case kComparisonMissing:
982             print_diff_with_missing_file(&outputStream, *diff, relativePath);
983             continue;
984           default:
985             SkDEBUGFAIL("encountered DiffRecord with unknown result type");
986             continue;
987         }
988
989         if (!diff->fBasePath.startsWith(PATH_DIV_STR)) {
990             diff->fBasePath.prepend(relativePath);
991         }
992         if (!diff->fComparisonPath.startsWith(PATH_DIV_STR)) {
993             diff->fComparisonPath.prepend(relativePath);
994         }
995
996         int height = compute_image_height(diff->fBaseHeight, diff->fBaseWidth);
997         outputStream.writeText("<tr>\n");
998         print_label_cell(&outputStream, *diff);
999         print_image_cell(&outputStream,
1000                          filename_to_white_filename(diff->fFilename), height);
1001         print_image_cell(&outputStream,
1002                          filename_to_diff_filename(diff->fFilename), height);
1003         print_image_cell(&outputStream, diff->fBasePath, height);
1004         print_image_cell(&outputStream, diff->fComparisonPath, height);
1005         outputStream.writeText("</tr>\n");
1006         outputStream.flush();
1007     }
1008     outputStream.writeText("</table>\n");
1009     outputStream.writeText("</body>\n</html>\n");
1010     outputStream.flush();
1011 }
1012
1013 static void usage (char * argv0) {
1014     SkDebugf("Skia baseline image diff tool\n");
1015     SkDebugf("\n"
1016 "Usage: \n"
1017 "    %s <baseDir> <comparisonDir> [outputDir] \n"
1018 , argv0, argv0);
1019     SkDebugf("\n"
1020 "Arguments: \n"
1021 "    --nodiffs: don't write out image diffs or index.html, just generate \n"
1022 "               report on stdout \n"
1023 "    --threshold <n>: only report differences > n (per color channel) [default 0]\n"
1024 "    --match: compare files whose filenames contain this substring; if \n"
1025 "             unspecified, compare ALL files. \n"
1026 "             this flag may be repeated to add more matching substrings. \n"
1027 "    --nomatch: regardless of --match, DO NOT compare files whose filenames \n"
1028 "               contain this substring. \n"
1029 "               this flag may be repeated to add more forbidden substrings. \n"
1030 "    --sortbymismatch: sort by average color channel mismatch\n");
1031     SkDebugf(
1032 "    --sortbymaxmismatch: sort by worst color channel mismatch;\n"
1033 "                         break ties with -sortbymismatch\n"
1034 "    [default sort is by fraction of pixels mismatching]\n");
1035     SkDebugf(
1036 "    --weighted: sort by # pixels different weighted by color difference\n");
1037     SkDebugf(
1038 "    baseDir: directory to read baseline images from.\n");
1039     SkDebugf(
1040 "    comparisonDir: directory to read comparison images from\n");
1041     SkDebugf(
1042 "    outputDir: directory to write difference images and index.html to; \n"
1043 "               defaults to comparisonDir \n");
1044 }
1045
1046 int main (int argc, char ** argv) {
1047     DiffMetricProc diffProc = compute_diff_pmcolor;
1048     int (*sortProc)(const void*, const void*) = SkCastForQSort(compare_diff_metrics);
1049
1050     // Maximum error tolerated in any one color channel in any one pixel before
1051     // a difference is reported.
1052     int colorThreshold = 0;
1053     SkString baseDir;
1054     SkString comparisonDir;
1055     SkString outputDir;
1056     StringArray matchSubstrings;
1057     StringArray nomatchSubstrings;
1058
1059     bool generateDiffs = true;
1060
1061     RecordArray differences;
1062     DiffSummary summary;
1063
1064     int i;
1065     int numUnflaggedArguments = 0;
1066     for (i = 1; i < argc; i++) {
1067         if (!strcmp(argv[i], "--help")) {
1068             usage(argv[0]);
1069             return 0;
1070         }
1071         if (!strcmp(argv[i], "--nodiffs")) {
1072             generateDiffs = false;
1073             continue;
1074         }
1075         if (!strcmp(argv[i], "--threshold")) {
1076             colorThreshold = atoi(argv[++i]);
1077             continue;
1078         }
1079         if (!strcmp(argv[i], "--match")) {
1080             matchSubstrings.push(new SkString(argv[++i]));
1081             continue;
1082         }
1083         if (!strcmp(argv[i], "--nomatch")) {
1084             nomatchSubstrings.push(new SkString(argv[++i]));
1085             continue;
1086         }
1087         if (!strcmp(argv[i], "--sortbymismatch")) {
1088             sortProc = SkCastForQSort(compare_diff_mean_mismatches);
1089             continue;
1090         }
1091         if (!strcmp(argv[i], "--sortbymaxmismatch")) {
1092             sortProc = SkCastForQSort(compare_diff_max_mismatches);
1093             continue;
1094         }
1095         if (!strcmp(argv[i], "--weighted")) {
1096             sortProc = SkCastForQSort(compare_diff_weighted);
1097             continue;
1098         }
1099         if (argv[i][0] != '-') {
1100             switch (numUnflaggedArguments++) {
1101                 case 0:
1102                     baseDir.set(argv[i]);
1103                     continue;
1104                 case 1:
1105                     comparisonDir.set(argv[i]);
1106                     continue;
1107                 case 2:
1108                     outputDir.set(argv[i]);
1109                     continue;
1110                 default:
1111                     SkDebugf("extra unflagged argument <%s>\n", argv[i]);
1112                     usage(argv[0]);
1113                     return 0;
1114             }
1115         }
1116
1117         SkDebugf("Unrecognized argument <%s>\n", argv[i]);
1118         usage(argv[0]);
1119         return 0;
1120     }
1121
1122     if (numUnflaggedArguments == 2) {
1123         outputDir = comparisonDir;
1124     } else if (numUnflaggedArguments != 3) {
1125         usage(argv[0]);
1126         return 0;
1127     }
1128
1129     if (!baseDir.endsWith(PATH_DIV_STR)) {
1130         baseDir.append(PATH_DIV_STR);
1131     }
1132     printf("baseDir is [%s]\n", baseDir.c_str());
1133
1134     if (!comparisonDir.endsWith(PATH_DIV_STR)) {
1135         comparisonDir.append(PATH_DIV_STR);
1136     }
1137     printf("comparisonDir is [%s]\n", comparisonDir.c_str());
1138
1139     if (!outputDir.endsWith(PATH_DIV_STR)) {
1140         outputDir.append(PATH_DIV_STR);
1141     }
1142     if (generateDiffs) {
1143         printf("writing diffs to outputDir is [%s]\n", outputDir.c_str());
1144     } else {
1145         printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str());
1146         outputDir.set("");
1147     }
1148
1149     // If no matchSubstrings were specified, match ALL strings
1150     // (except for whatever nomatchSubstrings were specified, if any).
1151     if (matchSubstrings.isEmpty()) {
1152         matchSubstrings.push(new SkString(""));
1153     }
1154
1155     create_diff_images(diffProc, colorThreshold, &differences,
1156                        baseDir, comparisonDir, outputDir,
1157                        matchSubstrings, nomatchSubstrings, &summary);
1158     summary.print();
1159
1160     if (differences.count()) {
1161         qsort(differences.begin(), differences.count(),
1162               sizeof(DiffRecord*), sortProc);
1163     }
1164
1165     if (generateDiffs) {
1166         print_diff_page(summary.fNumMatches, colorThreshold, differences,
1167                         baseDir, comparisonDir, outputDir);
1168     }
1169
1170     for (i = 0; i < differences.count(); i++) {
1171         delete differences[i];
1172     }
1173     matchSubstrings.deleteAll();
1174     nomatchSubstrings.deleteAll();
1175
1176     return summary.fNumMismatches;
1177 }