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