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