Automatic update of all copyright notices to reflect new license terms.
[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 struct DiffRecord {
36     DiffRecord (const SkString filename,
37                 const SkString basePath,
38                 const SkString comparisonPath)
39         : fFilename (filename)
40         , fBasePath (basePath)
41         , fComparisonPath (comparisonPath)
42         , fFractionDifference (0)
43         , fWeightedFraction (0)
44         , fAverageMismatchR (0)
45         , fAverageMismatchG (0)
46         , fAverageMismatchB (0)
47         , fMaxMismatchR (0)
48         , fMaxMismatchG (0)
49         , fMaxMismatchB (0) {
50         // These asserts are valid for GM, but not for --chromium
51         //SkASSERT(basePath.endsWith(filename.c_str()));
52         //SkASSERT(comparisonPath.endsWith(filename.c_str()));
53     };
54
55     SkString fFilename;
56     SkString fBasePath;
57     SkString fComparisonPath;
58
59     SkBitmap fBaseBitmap;
60     SkBitmap fComparisonBitmap;
61     SkBitmap fDifferenceBitmap;
62
63     /// Arbitrary floating-point metric to be used to sort images from most
64     /// to least different from baseline; values of 0 will be omitted from the
65     /// summary webpage.
66     float fFractionDifference;
67     float fWeightedFraction;
68
69     float fAverageMismatchR;
70     float fAverageMismatchG;
71     float fAverageMismatchB;
72
73     uint32_t fMaxMismatchR;
74     uint32_t fMaxMismatchG;
75     uint32_t fMaxMismatchB;
76 };
77
78 #define MAX2(a,b) (((b) < (a)) ? (a) : (b))
79 #define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
80
81 struct DiffSummary {
82     DiffSummary ()
83         : fNumMatches (0)
84         , fNumMismatches (0)
85         , fMaxMismatchV (0)
86         , fMaxMismatchPercent (0) { };
87
88     uint32_t fNumMatches;
89     uint32_t fNumMismatches;
90     uint32_t fMaxMismatchV;
91     float fMaxMismatchPercent;
92
93     void print () {
94         printf("%d of %d images matched.\n", fNumMatches,
95                fNumMatches + fNumMismatches);
96         if (fNumMismatches > 0) {
97             printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
98             printf("Largest area mismatch was %.2f%% of pixels\n",
99                    fMaxMismatchPercent);
100         }
101
102     }
103
104     void add (DiffRecord* drp) {
105         if (0 == drp->fFractionDifference) {
106             fNumMatches++;
107         } else {
108             fNumMismatches++;
109             if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
110                 fMaxMismatchPercent = drp->fFractionDifference * 100;
111             }
112             uint32_t value = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
113                                   drp->fMaxMismatchB);
114             if (value > fMaxMismatchV) {
115                 fMaxMismatchV = value;
116             }
117         }
118     }
119 };
120
121 typedef SkTDArray<DiffRecord*> RecordArray;
122
123 /// Comparison routine for qsort;  sorts by fFractionDifference
124 /// from largest to smallest.
125 static int compare_diff_metrics (DiffRecord** lhs, DiffRecord** rhs) {
126     if ((*lhs)->fFractionDifference < (*rhs)->fFractionDifference) {
127         return 1;
128     }
129     if ((*rhs)->fFractionDifference < (*lhs)->fFractionDifference) {
130         return -1;
131     }
132     return 0;
133 }
134
135 static int compare_diff_weighted (DiffRecord** lhs, DiffRecord** rhs) {
136     if ((*lhs)->fWeightedFraction < (*rhs)->fWeightedFraction) {
137         return 1;
138     }
139     if ((*lhs)->fWeightedFraction > (*rhs)->fWeightedFraction) {
140         return -1;
141     }
142     return 0;
143 }
144
145 /// Comparison routine for qsort;  sorts by max(fAverageMismatch{RGB})
146 /// from largest to smallest.
147 static int compare_diff_mean_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
148     float leftValue = MAX3((*lhs)->fAverageMismatchR,
149                            (*lhs)->fAverageMismatchG,
150                            (*lhs)->fAverageMismatchB);
151     float rightValue = MAX3((*rhs)->fAverageMismatchR,
152                             (*rhs)->fAverageMismatchG,
153                             (*rhs)->fAverageMismatchB);
154     if (leftValue < rightValue) {
155         return 1;
156     }
157     if (rightValue < leftValue) {
158         return -1;
159     }
160     return 0;
161 }
162
163 /// Comparison routine for qsort;  sorts by max(fMaxMismatch{RGB})
164 /// from largest to smallest.
165 static int compare_diff_max_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
166     float leftValue = MAX3((*lhs)->fMaxMismatchR,
167                            (*lhs)->fMaxMismatchG,
168                            (*lhs)->fMaxMismatchB);
169     float rightValue = MAX3((*rhs)->fMaxMismatchR,
170                             (*rhs)->fMaxMismatchG,
171                             (*rhs)->fMaxMismatchB);
172     if (leftValue < rightValue) {
173         return 1;
174     }
175     if (rightValue < leftValue) {
176         return -1;
177     }
178     return compare_diff_mean_mismatches(lhs, rhs);
179 }
180
181
182
183 /// Parameterized routine to compute the color of a pixel in a difference image.
184 typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
185
186 static bool get_bitmaps (DiffRecord* diffRecord) {
187     SkFILEStream compareStream(diffRecord->fComparisonPath.c_str());
188     if (!compareStream.isValid()) {
189         SkDebugf("WARNING: couldn't open comparison file <%s>\n",
190                  diffRecord->fComparisonPath.c_str());
191         return false;
192     }
193
194     SkFILEStream baseStream(diffRecord->fBasePath.c_str());
195     if (!baseStream.isValid()) {
196         SkDebugf("ERROR: couldn't open base file <%s>\n",
197                  diffRecord->fBasePath.c_str());
198         return false;
199     }
200
201     SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
202     if (NULL == codec) {
203         SkDebugf("ERROR: no codec found for <%s>\n",
204                  diffRecord->fBasePath.c_str());
205         return false;
206     }
207
208     SkAutoTDelete<SkImageDecoder> ad(codec);
209
210     baseStream.rewind();
211     if (!codec->decode(&baseStream, &diffRecord->fBaseBitmap,
212                        SkBitmap::kARGB_8888_Config,
213                        SkImageDecoder::kDecodePixels_Mode)) {
214         SkDebugf("ERROR: codec failed for <%s>\n",
215                  diffRecord->fBasePath.c_str());
216         return false;
217     }
218
219     if (!codec->decode(&compareStream, &diffRecord->fComparisonBitmap,
220                        SkBitmap::kARGB_8888_Config,
221                        SkImageDecoder::kDecodePixels_Mode)) {
222         SkDebugf("ERROR: codec failed for <%s>\n",
223                  diffRecord->fComparisonPath.c_str());
224         return false;
225     }
226
227     return true;
228 }
229
230 // from gm - thanks to PNG, we need to force all pixels 100% opaque
231 static void force_all_opaque(const SkBitmap& bitmap) {
232    SkAutoLockPixels lock(bitmap);
233    for (int y = 0; y < bitmap.height(); y++) {
234        for (int x = 0; x < bitmap.width(); x++) {
235            *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
236        }
237    }
238 }
239
240 // from gm
241 static bool write_bitmap(const SkString& path, const SkBitmap& bitmap) {
242     SkBitmap copy;
243     bitmap.copyTo(&copy, SkBitmap::kARGB_8888_Config);
244     force_all_opaque(copy);
245     return SkImageEncoder::EncodeFile(path.c_str(), copy,
246                                       SkImageEncoder::kPNG_Type, 100);
247 }
248
249 // from gm
250 static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
251     int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
252     int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
253     int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
254
255     return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
256 }
257
258 /// Returns white on every pixel so that differences jump out at you;
259 /// makes it easy to spot areas of difference that are in the least-significant
260 /// bits.
261 static inline SkPMColor compute_diff_white(SkPMColor c0, SkPMColor c1) {
262     return SkPackARGB32(0xFF, 0xFF, 0xFF, 0xFF);
263 }
264
265 static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1,
266                                             const int threshold) {
267     int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
268     int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
269     int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
270     int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
271
272     return ((SkAbs32(da) <= threshold) &&
273             (SkAbs32(dr) <= threshold) &&
274             (SkAbs32(dg) <= threshold) &&
275             (SkAbs32(db) <= threshold));
276 }
277
278 // based on gm
279 static void compute_diff(DiffRecord* dr,
280                          DiffMetricProc diffFunction,
281                          const int colorThreshold) {
282     SkAutoLockPixels alp(dr->fDifferenceBitmap);
283
284     const int w = dr->fComparisonBitmap.width();
285     const int h = dr->fComparisonBitmap.height();
286     int mismatchedPixels = 0;
287     int totalMismatchR = 0;
288     int totalMismatchG = 0;
289     int totalMismatchB = 0;
290     // Accumulate fractionally different pixels, then divide out
291     // # of pixels at the end.
292     dr->fWeightedFraction = 0;
293     for (int y = 0; y < h; y++) {
294         for (int x = 0; x < w; x++) {
295             SkPMColor c0 = *dr->fBaseBitmap.getAddr32(x, y);
296             SkPMColor c1 = *dr->fComparisonBitmap.getAddr32(x, y);
297             SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
298             SkPMColor outputDifference = diffFunction(c0, c1);
299             uint32_t thisR = SkGetPackedR32(trueDifference);
300             uint32_t thisG = SkGetPackedG32(trueDifference);
301             uint32_t thisB = SkGetPackedB32(trueDifference);
302             totalMismatchR += thisR;
303             totalMismatchG += thisG;
304             totalMismatchB += thisB;
305             // In HSV, value is defined as max RGB component.
306             int value = MAX3(thisR, thisG, thisB);
307             dr->fWeightedFraction += ((float) value) / 255;
308             if (thisR > dr->fMaxMismatchR) {
309                 dr->fMaxMismatchR = thisR;
310             }
311             if (thisG > dr->fMaxMismatchG) {
312                 dr->fMaxMismatchG = thisG;
313             }
314             if (thisB > dr->fMaxMismatchB) {
315                 dr->fMaxMismatchB = thisB;
316             }
317             if (!colors_match_thresholded(c0, c1, colorThreshold)) {
318                 mismatchedPixels++;
319                 *dr->fDifferenceBitmap.getAddr32(x, y) = outputDifference;
320             } else {
321                 *dr->fDifferenceBitmap.getAddr32(x, y) = 0;
322             }
323         }
324     }
325     int pixelCount = w * h;
326     dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
327     dr->fWeightedFraction /= pixelCount;
328     dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
329     dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
330     dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
331 }
332
333 /// Given a image filename, returns the name of the file containing the
334 /// associated difference image.
335 static SkString filename_to_diff_filename (const SkString& filename) {
336     SkString diffName (filename);
337     const char* cstring = diffName.c_str();
338     int dotOffset = strrchr(cstring, '.') - cstring;
339     diffName.remove(dotOffset, diffName.size() - dotOffset);
340     diffName.append("-diff.png");
341     return diffName;
342 }
343
344 /// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo-actual.png"
345 static SkString chrome_expected_path_to_actual (const SkString& expected) {
346     SkString actualPath (expected);
347     actualPath.remove(actualPath.size() - 13, 13);
348     actualPath.append("-actual.png");
349     return actualPath;
350 }
351
352 /// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo.png"
353 static SkString chrome_expected_name_to_short (const SkString& expected) {
354     SkString shortName (expected);
355     shortName.remove(shortName.size() - 13, 13);
356     shortName.append(".png");
357     return shortName;
358 }
359
360
361 static void create_and_write_diff_image(DiffRecord* drp,
362                                         DiffMetricProc dmp,
363                                         const int colorThreshold,
364                                         const SkString& outputDir,
365                                         const SkString& filename) {
366     const int w = drp->fBaseBitmap.width();
367     const int h = drp->fBaseBitmap.height();
368     drp->fDifferenceBitmap.setConfig(SkBitmap::kARGB_8888_Config, w, h);
369     drp->fDifferenceBitmap.allocPixels();
370     compute_diff(drp, dmp, colorThreshold);
371
372     SkString outPath (outputDir);
373     outPath.append(filename_to_diff_filename(filename));
374     write_bitmap(outPath, drp->fDifferenceBitmap);
375 }
376
377 /// Creates difference images, returns the number that have a 0 metric.
378 static void create_diff_images (DiffMetricProc dmp,
379                                 const int colorThreshold,
380                                 RecordArray* differences,
381                                 const SkString& baseDir,
382                                 const SkString& comparisonDir,
383                                 const SkString& outputDir,
384                                 DiffSummary* summary) {
385
386     //@todo thudson 19 Apr 2011
387     // this lets us know about files in baseDir not in compareDir, but it
388     // doesn't detect files in compareDir not in baseDir. Doing that
389     // efficiently seems to imply iterating through both directories to
390     // create a merged list, and then attempting to process every entry
391     // in that list?
392
393     SkOSFile::Iter baseIterator (baseDir.c_str());
394     SkString filename;
395     while (baseIterator.next(&filename)) {
396         if (filename.endsWith(".pdf")) {
397             continue;
398         }
399         SkString basePath (baseDir);
400         basePath.append(filename);
401         SkString comparisonPath (comparisonDir);
402         comparisonPath.append(filename);
403         DiffRecord * drp = new DiffRecord (filename, basePath, comparisonPath);
404         if (!get_bitmaps(drp)) {
405             continue;
406         }
407
408         create_and_write_diff_image(drp, dmp, colorThreshold,
409                                     outputDir, filename);
410
411         differences->push(drp);
412         summary->add(drp);
413     }
414 }
415
416 static void create_diff_images_chromium (DiffMetricProc dmp,
417                                          const int colorThreshold,
418                                          RecordArray* differences,
419                                          const SkString& dirname,
420                                          const SkString& outputDir,
421                                          DiffSummary* summary) {
422     SkOSFile::Iter baseIterator (dirname.c_str());
423     SkString filename;
424     while (baseIterator.next(&filename)) {
425         if (filename.endsWith(".pdf")) {
426             continue;
427         }
428         if (filename.endsWith("-expected.png")) {
429             SkString expectedPath (dirname);
430             expectedPath.append(filename);
431             SkString shortName (chrome_expected_name_to_short(filename));
432             SkString actualPath (chrome_expected_path_to_actual(expectedPath));
433             DiffRecord * drp =
434                 new DiffRecord (shortName, expectedPath, actualPath);
435             if (!get_bitmaps(drp)) {
436                 continue;
437             }
438             create_and_write_diff_image(drp, dmp, colorThreshold,
439                                         outputDir, shortName);
440
441             differences->push(drp);
442             summary->add(drp);
443         }
444     }
445 }
446
447 static void analyze_chromium(DiffMetricProc dmp,
448                              const int colorThreshold,
449                              RecordArray* differences,
450                              const SkString& dirname,
451                              const SkString& outputDir,
452                              DiffSummary* summary) {
453     create_diff_images_chromium(dmp, colorThreshold, differences,
454                                 dirname, outputDir, summary);
455     SkOSFile::Iter dirIterator(dirname.c_str());
456     SkString newdirname;
457     while (dirIterator.next(&newdirname, true)) {
458         if (newdirname.startsWith(".")) {
459             continue;
460         }
461         SkString fullname (dirname);
462         fullname.append(newdirname);
463         if (!fullname.endsWith("/")) {
464             fullname.append("/");
465         }
466         analyze_chromium(dmp, colorThreshold, differences,
467                          fullname, outputDir, summary);
468     }
469 }
470
471 /// Make layout more consistent by scaling image to 240 height, 360 width,
472 /// or natural size, whichever is smallest.
473 static int compute_image_height (const SkBitmap& bmp) {
474     int retval = 240;
475     if (bmp.height() < retval) {
476         retval = bmp.height();
477     }
478     float scale = (float) retval / bmp.height();
479     if (bmp.width() * scale > 360) {
480         scale = (float) 360 / bmp.width();
481         retval = bmp.height() * scale;
482     }
483     return retval;
484 }
485
486 static void print_page_header (SkFILEWStream* stream,
487                                const int matchCount,
488                                const int colorThreshold,
489                                const RecordArray& differences) {
490     SkTime::DateTime dt;
491     SkTime::GetDateTime(&dt);
492     stream->writeText("SkDiff run at ");
493     stream->writeDecAsText(dt.fHour);
494     stream->writeText(":");
495     if (dt.fMinute < 10) {
496         stream->writeText("0");
497     }
498     stream->writeDecAsText(dt.fMinute);
499     stream->writeText(":");
500     if (dt.fSecond < 10) {
501         stream->writeText("0");
502     }
503     stream->writeDecAsText(dt.fSecond);
504     stream->writeText("<br>");
505     stream->writeDecAsText(matchCount);
506     stream->writeText(" of ");
507     stream->writeDecAsText(differences.count());
508     stream->writeText(" images matched ");
509     if (colorThreshold == 0) {
510         stream->writeText("exactly");
511     } else {
512         stream->writeText("within ");
513         stream->writeDecAsText(colorThreshold);
514         stream->writeText(" color units per component");
515     }
516     stream->writeText(".<br>");
517
518 }
519
520 static void print_pixel_count (SkFILEWStream* stream,
521                                const DiffRecord& diff) {
522     stream->writeText("<br>(");
523     stream->writeDecAsText(diff.fFractionDifference *
524                            diff.fBaseBitmap.width() *
525                            diff.fBaseBitmap.height());
526     stream->writeText(" pixels)");
527 /*
528     stream->writeDecAsText(diff.fWeightedFraction *
529                            diff.fBaseBitmap.width() *
530                            diff.fBaseBitmap.height());
531     stream->writeText(" weighted pixels)");
532 */
533 }
534
535 static void print_label_cell (SkFILEWStream* stream,
536                               const DiffRecord& diff) {
537     stream->writeText("<td>");
538     stream->writeText(diff.fFilename.c_str());
539     stream->writeText("<br>");
540     char metricBuf [20];
541     sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
542     stream->writeText(metricBuf);
543     stream->writeText(" of pixels differ");
544     stream->writeText("\n  (");
545     sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
546     stream->writeText(metricBuf);
547     stream->writeText(" weighted)");
548     // Write the actual number of pixels that differ if it's < 1%
549     if (diff.fFractionDifference < 0.01) {
550         print_pixel_count(stream, diff);
551     }
552     stream->writeText("<br>Average color mismatch ");
553     stream->writeDecAsText(MAX3(diff.fAverageMismatchR,
554                                 diff.fAverageMismatchG,
555                                 diff.fAverageMismatchB));
556     stream->writeText("<br>Max color mismatch ");
557     stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
558                                 diff.fMaxMismatchG,
559                                 diff.fMaxMismatchB));
560     stream->writeText("</td>");
561 }
562
563 static void print_image_cell (SkFILEWStream* stream,
564                               const SkString& path,
565                               int height) {
566     stream->writeText("<td><a href=\"");
567     stream->writeText(path.c_str());
568     stream->writeText("\"><img src=\"");
569     stream->writeText(path.c_str());
570     stream->writeText("\" height=\"");
571     stream->writeDecAsText(height);
572     stream->writeText("px\"></a></td>");
573 }
574
575 static void print_diff_page (const int matchCount,
576                              const int colorThreshold,
577                              const RecordArray& differences,
578                              const SkString& baseDir,
579                              const SkString& comparisonDir,
580                              const SkString& outputDir) {
581
582     SkString outputPath (outputDir);
583     outputPath.append("index.html");
584     //SkFILEWStream outputStream ("index.html");
585     SkFILEWStream outputStream (outputPath.c_str());
586
587     // Need to convert paths from relative-to-cwd to relative-to-outputDir
588     // FIXME this doesn't work if there are '..' inside the outputDir
589     unsigned int ui;
590     SkString relativePath;
591     for (ui = 0; ui < outputDir.size(); ui++) {
592         if (outputDir[ui] == '/') {
593             relativePath.append("../");
594         }
595     }
596
597     outputStream.writeText("<html>\n<body>\n");
598     print_page_header(&outputStream, matchCount, colorThreshold, differences);
599
600     outputStream.writeText("<table>\n");
601     int i;
602     for (i = 0; i < differences.count(); i++) {
603         DiffRecord* diff = differences[i];
604         if (0 == diff->fFractionDifference) {
605             continue;
606         }
607         if (!diff->fBasePath.startsWith("/")) {
608             diff->fBasePath.prepend(relativePath);
609         }
610         if (!diff->fComparisonPath.startsWith("/")) {
611             diff->fComparisonPath.prepend(relativePath);
612         }
613         int height = compute_image_height(diff->fBaseBitmap);
614         outputStream.writeText("<tr>\n");
615         print_label_cell(&outputStream, *diff);
616         print_image_cell(&outputStream, diff->fBasePath, height);
617         print_image_cell(&outputStream,
618                          filename_to_diff_filename(diff->fFilename), height);
619         print_image_cell(&outputStream, diff->fComparisonPath, height);
620         outputStream.writeText("</tr>\n");
621         outputStream.flush();
622     }
623     outputStream.writeText("</table>\n");
624     outputStream.writeText("</body>\n</html>\n");
625     outputStream.flush();
626 }
627
628 static void usage (char * argv0) {
629     SkDebugf("Skia baseline image diff tool\n");
630     SkDebugf("Usage: %s baseDir comparisonDir [outputDir]\n", argv0);
631     SkDebugf(
632 "       %s --chromium --release|--debug baseDir outputDir\n", argv0);
633     SkDebugf(
634 "    --white: force all difference pixels to white\n"
635 "    --threshold n: only report differences > n (in one channel) [default 0]\n"
636 "    --sortbymismatch: sort by average color channel mismatch\n");
637     SkDebugf(
638 "    --sortbymaxmismatch: sort by worst color channel mismatch,\n"
639 "                        break ties with -sortbymismatch,\n"
640 "        [default by fraction of pixels mismatching]\n");
641     SkDebugf(
642 "    --weighted: sort by # pixels different weighted by color difference\n");
643     SkDebugf(
644 "    --chromium-release: process Webkit LayoutTests results instead of gm\n"
645 "    --chromium-debug: process Webkit LayoutTests results instead of gm\n");
646     SkDebugf(
647 "    baseDir: directory to read baseline images from,\n"
648 "             or chromium/src directory for --chromium.\n");
649     SkDebugf("    comparisonDir: directory to read comparison images from\n");
650     SkDebugf(
651 "    outputDir: directory to write difference images to; defaults to\n"
652 "               comparisonDir when not running --chromium\n");
653     SkDebugf("Also creates an \"index.html\" file in the output directory.\n");
654 }
655
656 int main (int argc, char ** argv) {
657     DiffMetricProc diffProc = compute_diff_pmcolor;
658     SkQSortCompareProc sortProc = (SkQSortCompareProc) compare_diff_metrics;
659
660     // Maximum error tolerated in any one color channel in any one pixel before
661     // a difference is reported.
662     int colorThreshold = 0;
663     SkString baseDir;
664     SkString comparisonDir;
665     SkString outputDir;
666
667     bool analyzeChromium = false;
668     bool chromiumDebug = false;
669     bool chromiumRelease = false;
670
671     RecordArray differences;
672     DiffSummary summary;
673
674     int i, j;
675     for (i = 1, j = 0; i < argc; i++) {
676         if (!strcmp(argv[i], "--help")) {
677             usage(argv[0]);
678             return 0;
679         }
680         if (!strcmp(argv[i], "--white")) {
681             diffProc = compute_diff_white;
682             continue;
683         }
684         if (!strcmp(argv[i], "--sortbymismatch")) {
685             sortProc = (SkQSortCompareProc) compare_diff_mean_mismatches;
686             continue;
687         }
688         if (!strcmp(argv[i], "--sortbymaxmismatch")) {
689             sortProc = (SkQSortCompareProc) compare_diff_max_mismatches;
690             continue;
691         }
692         if (!strcmp(argv[i], "--weighted")) {
693             sortProc = (SkQSortCompareProc) compare_diff_weighted;
694             continue;
695         }
696         if (!strcmp(argv[i], "--chromium-release")) {
697             analyzeChromium = true;
698             chromiumRelease = true;
699             continue;
700         }
701         if (!strcmp(argv[i], "--chromium-debug")) {
702             analyzeChromium = true;
703             chromiumDebug = true;
704             continue;
705         }
706         if (argv[i][0] != '-') {
707             switch (j++) {
708                 case 0:
709                     baseDir.set(argv[i]);
710                     continue;
711                 case 1:
712                     comparisonDir.set(argv[i]);
713                     continue;
714                 case 2:
715                     outputDir.set(argv[i]);
716                     continue;
717                 default:
718                     usage(argv[0]);
719                     return 0;
720             }
721         }
722
723         SkDebugf("Unrecognized argument <%s>\n", argv[i]);
724         usage(argv[0]);
725         return 0;
726     }
727     if (analyzeChromium) {
728         if (j != 2) {
729             usage(argv[0]);
730             return 0;
731         }
732         if (chromiumRelease && chromiumDebug) {
733             SkDebugf(
734 "--chromium must be either -release or -debug, not both!\n");
735             return 0;
736         }
737     }
738
739     if (j == 2) {
740         outputDir = comparisonDir;
741     } else if (j != 3) {
742         usage(argv[0]);
743         return 0;
744     }
745
746     if (!baseDir.endsWith("/")) {
747         baseDir.append("/");
748     }
749     if (!comparisonDir.endsWith("/")) {
750         comparisonDir.append("/");
751     }
752     if (!outputDir.endsWith("/")) {
753         outputDir.append("/");
754     }
755
756     if (analyzeChromium) {
757         baseDir.append("webkit/");
758         if (chromiumRelease) {
759             baseDir.append("Release/");
760         }
761         if (chromiumDebug) {
762             baseDir.append("Debug/");
763         }
764         baseDir.append("layout-test-results/");
765         analyze_chromium(diffProc, colorThreshold, &differences,
766                          baseDir, outputDir, &summary);
767     } else {
768         create_diff_images(diffProc, colorThreshold, &differences,
769                            baseDir, comparisonDir, outputDir, &summary);
770     }
771     summary.print();
772
773     if (differences.count()) {
774         SkQSort(differences.begin(), differences.count(),
775             sizeof(DiffRecord*), sortProc);
776     }
777     print_diff_page(summary.fNumMatches, colorThreshold, differences,
778                     baseDir, comparisonDir, outputDir);
779
780     for (i = 0; i < differences.count(); i++) {
781         delete differences[i];
782     }
783 }