blob: 6bc2eebca45fd0077fc6aff2ddae7cb825a70be8 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
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 */
tomhudson@google.com4b33d282011-04-27 15:39:30 +00008#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.
tomhudson@google.com7d042802011-07-14 13:15:55 +000026 * Creates an index.html in the current third directory to compare each
tomhudson@google.com4b33d282011-04-27 15:39:30 +000027 * pair that does not match exactly.
28 * Does *not* recursively descend directories.
tomhudson@google.com7d042802011-07-14 13:15:55 +000029 *
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.
tomhudson@google.com4b33d282011-04-27 15:39:30 +000033 */
34
35struct DiffRecord {
tomhudson@google.com4e305982011-07-13 17:42:46 +000036 DiffRecord (const SkString filename,
37 const SkString basePath,
38 const SkString comparisonPath)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000039 : fFilename (filename)
tomhudson@google.com4e305982011-07-13 17:42:46 +000040 , fBasePath (basePath)
41 , fComparisonPath (comparisonPath)
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000042 , fFractionDifference (0)
43 , fWeightedFraction (0)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000044 , fAverageMismatchR (0)
45 , fAverageMismatchG (0)
46 , fAverageMismatchB (0)
47 , fMaxMismatchR (0)
48 , fMaxMismatchG (0)
tomhudson@google.com4e305982011-07-13 17:42:46 +000049 , fMaxMismatchB (0) {
tomhudson@google.com7d042802011-07-14 13:15:55 +000050 // These asserts are valid for GM, but not for --chromium
51 //SkASSERT(basePath.endsWith(filename.c_str()));
52 //SkASSERT(comparisonPath.endsWith(filename.c_str()));
tomhudson@google.com4e305982011-07-13 17:42:46 +000053 };
tomhudson@google.com4b33d282011-04-27 15:39:30 +000054
55 SkString fFilename;
tomhudson@google.com4e305982011-07-13 17:42:46 +000056 SkString fBasePath;
57 SkString fComparisonPath;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000058
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.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000066 float fFractionDifference;
67 float fWeightedFraction;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000068
69 float fAverageMismatchR;
70 float fAverageMismatchG;
71 float fAverageMismatchB;
72
73 uint32_t fMaxMismatchR;
74 uint32_t fMaxMismatchG;
75 uint32_t fMaxMismatchB;
76};
77
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000078#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
79#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
80
81struct 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 }
tomhudson@google.com88a0e052011-06-09 18:54:01 +0000112 uint32_t value = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
113 drp->fMaxMismatchB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000114 if (value > fMaxMismatchV) {
115 fMaxMismatchV = value;
116 }
117 }
118 }
119};
120
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000121typedef SkTDArray<DiffRecord*> RecordArray;
122
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000123/// Comparison routine for qsort; sorts by fFractionDifference
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000124/// from largest to smallest.
125static int compare_diff_metrics (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000126 if ((*lhs)->fFractionDifference < (*rhs)->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000127 return 1;
128 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000129 if ((*rhs)->fFractionDifference < (*lhs)->fFractionDifference) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000130 return -1;
131 }
132 return 0;
133}
134
135static int compare_diff_weighted (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000136 if ((*lhs)->fWeightedFraction < (*rhs)->fWeightedFraction) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000137 return 1;
138 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000139 if ((*lhs)->fWeightedFraction > (*rhs)->fWeightedFraction) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000140 return -1;
141 }
142 return 0;
143}
144
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000145/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
146/// from largest to smallest.
147static 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.
165static 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.
184typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
185
tomhudson@google.com4e305982011-07-13 17:42:46 +0000186static bool get_bitmaps (DiffRecord* diffRecord) {
187 SkFILEStream compareStream(diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000188 if (!compareStream.isValid()) {
189 SkDebugf("WARNING: couldn't open comparison file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000190 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000191 return false;
192 }
193
tomhudson@google.com4e305982011-07-13 17:42:46 +0000194 SkFILEStream baseStream(diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000195 if (!baseStream.isValid()) {
196 SkDebugf("ERROR: couldn't open base file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000197 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000198 return false;
199 }
200
201 SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
202 if (NULL == codec) {
203 SkDebugf("ERROR: no codec found for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000204 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000205 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",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000215 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000216 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",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000223 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000224 return false;
225 }
226
227 return true;
228}
229
230// from gm - thanks to PNG, we need to force all pixels 100% opaque
231static 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
241static 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
250static 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.
261static inline SkPMColor compute_diff_white(SkPMColor c0, SkPMColor c1) {
262 return SkPackARGB32(0xFF, 0xFF, 0xFF, 0xFF);
263}
264
265static 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
279static 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;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000290 // Accumulate fractionally different pixels, then divide out
291 // # of pixels at the end.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000292 dr->fWeightedFraction = 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000293 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);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000297 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);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000302 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);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000307 dr->fWeightedFraction += ((float) value) / 255;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000308 if (thisR > dr->fMaxMismatchR) {
309 dr->fMaxMismatchR = thisR;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000310 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000311 if (thisG > dr->fMaxMismatchG) {
312 dr->fMaxMismatchG = thisG;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000313 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000314 if (thisB > dr->fMaxMismatchB) {
315 dr->fMaxMismatchB = thisB;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000316 }
317 if (!colors_match_thresholded(c0, c1, colorThreshold)) {
318 mismatchedPixels++;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000319 *dr->fDifferenceBitmap.getAddr32(x, y) = outputDifference;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000320 } else {
321 *dr->fDifferenceBitmap.getAddr32(x, y) = 0;
322 }
323 }
324 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000325 int pixelCount = w * h;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000326 dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
327 dr->fWeightedFraction /= pixelCount;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000328 dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
329 dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
330 dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000331}
332
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000333/// Given a image filename, returns the name of the file containing the
334/// associated difference image.
335static 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
tomhudson@google.com7d042802011-07-14 13:15:55 +0000344/// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo-actual.png"
345static 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"
353static 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
361static void create_and_write_diff_image(DiffRecord* drp,
362 DiffMetricProc dmp,
363 const int colorThreshold,
364 const SkString& outputDir,
365 const SkString& filename) {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000366 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);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000371
372 SkString outPath (outputDir);
373 outPath.append(filename_to_diff_filename(filename));
374 write_bitmap(outPath, drp->fDifferenceBitmap);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000375}
376
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000377/// Creates difference images, returns the number that have a 0 metric.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000378static void create_diff_images (DiffMetricProc dmp,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000379 const int colorThreshold,
380 RecordArray* differences,
381 const SkString& baseDir,
382 const SkString& comparisonDir,
383 const SkString& outputDir,
384 DiffSummary* summary) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000385
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;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000395 while (baseIterator.next(&filename)) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000396 if (filename.endsWith(".pdf")) {
397 continue;
398 }
tomhudson@google.com4e305982011-07-13 17:42:46 +0000399 SkString basePath (baseDir);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000400 basePath.append(filename);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000401 SkString comparisonPath (comparisonDir);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000402 comparisonPath.append(filename);
403 DiffRecord * drp = new DiffRecord (filename, basePath, comparisonPath);
404 if (!get_bitmaps(drp)) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000405 continue;
406 }
407
tomhudson@google.com7d042802011-07-14 13:15:55 +0000408 create_and_write_diff_image(drp, dmp, colorThreshold,
409 outputDir, filename);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000410
411 differences->push(drp);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000412 summary->add(drp);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000413 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000414}
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000415
tomhudson@google.com7d042802011-07-14 13:15:55 +0000416static 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
447static 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 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000469}
470
471/// Make layout more consistent by scaling image to 240 height, 360 width,
472/// or natural size, whichever is smallest.
473static 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
486static 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
520static void print_pixel_count (SkFILEWStream* stream,
521 const DiffRecord& diff) {
522 stream->writeText("<br>(");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000523 stream->writeDecAsText(diff.fFractionDifference *
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000524 diff.fBaseBitmap.width() *
525 diff.fBaseBitmap.height());
526 stream->writeText(" pixels)");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000527/*
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000528 stream->writeDecAsText(diff.fWeightedFraction *
tomhudson@google.com5b325292011-05-24 19:41:13 +0000529 diff.fBaseBitmap.width() *
530 diff.fBaseBitmap.height());
531 stream->writeText(" weighted pixels)");
532*/
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000533}
534
535static 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];
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000541 sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000542 stream->writeText(metricBuf);
543 stream->writeText(" of pixels differ");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000544 stream->writeText("\n (");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000545 sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000546 stream->writeText(metricBuf);
547 stream->writeText(" weighted)");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000548 // Write the actual number of pixels that differ if it's < 1%
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000549 if (diff.fFractionDifference < 0.01) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000550 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
563static void print_image_cell (SkFILEWStream* stream,
tomhudson@google.com4e305982011-07-13 17:42:46 +0000564 const SkString& path,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000565 int height) {
566 stream->writeText("<td><a href=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000567 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000568 stream->writeText("\"><img src=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000569 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000570 stream->writeText("\" height=\"");
571 stream->writeDecAsText(height);
572 stream->writeText("px\"></a></td>");
573}
574
575static 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
tomhudson@google.com5b325292011-05-24 19:41:13 +0000582 SkString outputPath (outputDir);
583 outputPath.append("index.html");
584 //SkFILEWStream outputStream ("index.html");
585 SkFILEWStream outputStream (outputPath.c_str());
586
tomhudson@google.com4e305982011-07-13 17:42:46 +0000587 // Need to convert paths from relative-to-cwd to relative-to-outputDir
tomhudson@google.com5b325292011-05-24 19:41:13 +0000588 // 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 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000596
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];
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000604 if (0 == diff->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000605 continue;
606 }
tomhudson@google.com4e305982011-07-13 17:42:46 +0000607 if (!diff->fBasePath.startsWith("/")) {
608 diff->fBasePath.prepend(relativePath);
609 }
610 if (!diff->fComparisonPath.startsWith("/")) {
611 diff->fComparisonPath.prepend(relativePath);
612 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000613 int height = compute_image_height(diff->fBaseBitmap);
614 outputStream.writeText("<tr>\n");
615 print_label_cell(&outputStream, *diff);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000616 print_image_cell(&outputStream, diff->fBasePath, height);
617 print_image_cell(&outputStream,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000618 filename_to_diff_filename(diff->fFilename), height);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000619 print_image_cell(&outputStream, diff->fComparisonPath, height);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000620 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
628static void usage (char * argv0) {
629 SkDebugf("Skia baseline image diff tool\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000630 SkDebugf("Usage: %s baseDir comparisonDir [outputDir]\n", argv0);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000631 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000632" %s --chromium --release|--debug baseDir outputDir\n", argv0);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000633 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000634" --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"
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000639" break ties with -sortbymismatch,\n"
640" [default by fraction of pixels mismatching]\n");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000641 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000642" --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");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000649 SkDebugf(" comparisonDir: directory to read comparison images from\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000650 SkDebugf(
651" outputDir: directory to write difference images to; defaults to\n"
tomhudson@google.com7d042802011-07-14 13:15:55 +0000652" comparisonDir when not running --chromium\n");
653 SkDebugf("Also creates an \"index.html\" file in the output directory.\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000654}
655
656int 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
tomhudson@google.com7d042802011-07-14 13:15:55 +0000667 bool analyzeChromium = false;
668 bool chromiumDebug = false;
669 bool chromiumRelease = false;
670
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000671 RecordArray differences;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000672 DiffSummary summary;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000673
674 int i, j;
675 for (i = 1, j = 0; i < argc; i++) {
tomhudson@google.com7d042802011-07-14 13:15:55 +0000676 if (!strcmp(argv[i], "--help")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000677 usage(argv[0]);
678 return 0;
679 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000680 if (!strcmp(argv[i], "--white")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000681 diffProc = compute_diff_white;
682 continue;
683 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000684 if (!strcmp(argv[i], "--sortbymismatch")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000685 sortProc = (SkQSortCompareProc) compare_diff_mean_mismatches;
686 continue;
687 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000688 if (!strcmp(argv[i], "--sortbymaxmismatch")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000689 sortProc = (SkQSortCompareProc) compare_diff_max_mismatches;
690 continue;
691 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000692 if (!strcmp(argv[i], "--weighted")) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000693 sortProc = (SkQSortCompareProc) compare_diff_weighted;
694 continue;
695 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000696 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;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000704 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 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000727 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
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000739 if (j == 2) {
740 outputDir = comparisonDir;
741 } else if (j != 3) {
742 usage(argv[0]);
743 return 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000744 }
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
tomhudson@google.com7d042802011-07-14 13:15:55 +0000756 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 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000771 summary.print();
tomhudson@google.com7d042802011-07-14 13:15:55 +0000772
773 if (differences.count()) {
774 SkQSort(differences.begin(), differences.count(),
775 sizeof(DiffRecord*), sortProc);
776 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000777 print_diff_page(summary.fNumMatches, colorThreshold, differences,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000778 baseDir, comparisonDir, outputDir);
779
780 for (i = 0; i < differences.count(); i++) {
781 delete differences[i];
782 }
783}