blob: 6541ec35243190b57340862cc72328177f8b8ff1 [file] [log] [blame]
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001#include "SkColorPriv.h"
2#include "SkImageDecoder.h"
3#include "SkImageEncoder.h"
4#include "SkOSFile.h"
5#include "SkStream.h"
6#include "SkTDArray.h"
7#include "SkTemplates.h"
8#include "SkTime.h"
9#include "SkTSearch.h"
10#include "SkTypes.h"
11
12/**
13 * skdiff
14 *
15 * Given three directory names, expects to find identically-named files in
16 * each of the first two; the first are treated as a set of baseline,
17 * the second a set of variant images, and a diff image is written into the
18 * third directory for each pair.
tomhudson@google.com7d042802011-07-14 13:15:55 +000019 * Creates an index.html in the current third directory to compare each
tomhudson@google.com4b33d282011-04-27 15:39:30 +000020 * pair that does not match exactly.
21 * Does *not* recursively descend directories.
tomhudson@google.com7d042802011-07-14 13:15:55 +000022 *
23 * With the --chromium flag, *does* recursively descend the first directory
24 * named, comparing *-expected.png with *-actual.png and writing diff
25 * images into the second directory, also writing index.html there.
tomhudson@google.com4b33d282011-04-27 15:39:30 +000026 */
27
28struct DiffRecord {
tomhudson@google.com4e305982011-07-13 17:42:46 +000029 DiffRecord (const SkString filename,
30 const SkString basePath,
31 const SkString comparisonPath)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000032 : fFilename (filename)
tomhudson@google.com4e305982011-07-13 17:42:46 +000033 , fBasePath (basePath)
34 , fComparisonPath (comparisonPath)
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000035 , fFractionDifference (0)
36 , fWeightedFraction (0)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000037 , fAverageMismatchR (0)
38 , fAverageMismatchG (0)
39 , fAverageMismatchB (0)
40 , fMaxMismatchR (0)
41 , fMaxMismatchG (0)
tomhudson@google.com4e305982011-07-13 17:42:46 +000042 , fMaxMismatchB (0) {
tomhudson@google.com7d042802011-07-14 13:15:55 +000043 // These asserts are valid for GM, but not for --chromium
44 //SkASSERT(basePath.endsWith(filename.c_str()));
45 //SkASSERT(comparisonPath.endsWith(filename.c_str()));
tomhudson@google.com4e305982011-07-13 17:42:46 +000046 };
tomhudson@google.com4b33d282011-04-27 15:39:30 +000047
48 SkString fFilename;
tomhudson@google.com4e305982011-07-13 17:42:46 +000049 SkString fBasePath;
50 SkString fComparisonPath;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000051
52 SkBitmap fBaseBitmap;
53 SkBitmap fComparisonBitmap;
54 SkBitmap fDifferenceBitmap;
55
56 /// Arbitrary floating-point metric to be used to sort images from most
57 /// to least different from baseline; values of 0 will be omitted from the
58 /// summary webpage.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000059 float fFractionDifference;
60 float fWeightedFraction;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000061
62 float fAverageMismatchR;
63 float fAverageMismatchG;
64 float fAverageMismatchB;
65
66 uint32_t fMaxMismatchR;
67 uint32_t fMaxMismatchG;
68 uint32_t fMaxMismatchB;
69};
70
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000071#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
72#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
73
74struct DiffSummary {
75 DiffSummary ()
76 : fNumMatches (0)
77 , fNumMismatches (0)
78 , fMaxMismatchV (0)
79 , fMaxMismatchPercent (0) { };
80
81 uint32_t fNumMatches;
82 uint32_t fNumMismatches;
83 uint32_t fMaxMismatchV;
84 float fMaxMismatchPercent;
85
86 void print () {
87 printf("%d of %d images matched.\n", fNumMatches,
88 fNumMatches + fNumMismatches);
89 if (fNumMismatches > 0) {
90 printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
91 printf("Largest area mismatch was %.2f%% of pixels\n",
92 fMaxMismatchPercent);
93 }
94
95 }
96
97 void add (DiffRecord* drp) {
98 if (0 == drp->fFractionDifference) {
99 fNumMatches++;
100 } else {
101 fNumMismatches++;
102 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
103 fMaxMismatchPercent = drp->fFractionDifference * 100;
104 }
tomhudson@google.com88a0e052011-06-09 18:54:01 +0000105 uint32_t value = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
106 drp->fMaxMismatchB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000107 if (value > fMaxMismatchV) {
108 fMaxMismatchV = value;
109 }
110 }
111 }
112};
113
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000114typedef SkTDArray<DiffRecord*> RecordArray;
115
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000116/// Comparison routine for qsort; sorts by fFractionDifference
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000117/// from largest to smallest.
118static int compare_diff_metrics (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000119 if ((*lhs)->fFractionDifference < (*rhs)->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000120 return 1;
121 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000122 if ((*rhs)->fFractionDifference < (*lhs)->fFractionDifference) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000123 return -1;
124 }
125 return 0;
126}
127
128static int compare_diff_weighted (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000129 if ((*lhs)->fWeightedFraction < (*rhs)->fWeightedFraction) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000130 return 1;
131 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000132 if ((*lhs)->fWeightedFraction > (*rhs)->fWeightedFraction) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000133 return -1;
134 }
135 return 0;
136}
137
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000138/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
139/// from largest to smallest.
140static int compare_diff_mean_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
141 float leftValue = MAX3((*lhs)->fAverageMismatchR,
142 (*lhs)->fAverageMismatchG,
143 (*lhs)->fAverageMismatchB);
144 float rightValue = MAX3((*rhs)->fAverageMismatchR,
145 (*rhs)->fAverageMismatchG,
146 (*rhs)->fAverageMismatchB);
147 if (leftValue < rightValue) {
148 return 1;
149 }
150 if (rightValue < leftValue) {
151 return -1;
152 }
153 return 0;
154}
155
156/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB})
157/// from largest to smallest.
158static int compare_diff_max_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
159 float leftValue = MAX3((*lhs)->fMaxMismatchR,
160 (*lhs)->fMaxMismatchG,
161 (*lhs)->fMaxMismatchB);
162 float rightValue = MAX3((*rhs)->fMaxMismatchR,
163 (*rhs)->fMaxMismatchG,
164 (*rhs)->fMaxMismatchB);
165 if (leftValue < rightValue) {
166 return 1;
167 }
168 if (rightValue < leftValue) {
169 return -1;
170 }
171 return compare_diff_mean_mismatches(lhs, rhs);
172}
173
174
175
176/// Parameterized routine to compute the color of a pixel in a difference image.
177typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
178
tomhudson@google.com4e305982011-07-13 17:42:46 +0000179static bool get_bitmaps (DiffRecord* diffRecord) {
180 SkFILEStream compareStream(diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000181 if (!compareStream.isValid()) {
182 SkDebugf("WARNING: couldn't open comparison file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000183 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000184 return false;
185 }
186
tomhudson@google.com4e305982011-07-13 17:42:46 +0000187 SkFILEStream baseStream(diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000188 if (!baseStream.isValid()) {
189 SkDebugf("ERROR: couldn't open base file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000190 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000191 return false;
192 }
193
194 SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
195 if (NULL == codec) {
196 SkDebugf("ERROR: no codec found for <%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 SkAutoTDelete<SkImageDecoder> ad(codec);
202
203 baseStream.rewind();
204 if (!codec->decode(&baseStream, &diffRecord->fBaseBitmap,
205 SkBitmap::kARGB_8888_Config,
206 SkImageDecoder::kDecodePixels_Mode)) {
207 SkDebugf("ERROR: codec failed for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000208 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000209 return false;
210 }
211
212 if (!codec->decode(&compareStream, &diffRecord->fComparisonBitmap,
213 SkBitmap::kARGB_8888_Config,
214 SkImageDecoder::kDecodePixels_Mode)) {
215 SkDebugf("ERROR: codec failed for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000216 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000217 return false;
218 }
219
220 return true;
221}
222
223// from gm - thanks to PNG, we need to force all pixels 100% opaque
224static void force_all_opaque(const SkBitmap& bitmap) {
225 SkAutoLockPixels lock(bitmap);
226 for (int y = 0; y < bitmap.height(); y++) {
227 for (int x = 0; x < bitmap.width(); x++) {
228 *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
229 }
230 }
231}
232
233// from gm
234static bool write_bitmap(const SkString& path, const SkBitmap& bitmap) {
235 SkBitmap copy;
236 bitmap.copyTo(&copy, SkBitmap::kARGB_8888_Config);
237 force_all_opaque(copy);
238 return SkImageEncoder::EncodeFile(path.c_str(), copy,
239 SkImageEncoder::kPNG_Type, 100);
240}
241
242// from gm
243static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
244 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
245 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
246 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
247
248 return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
249}
250
251/// Returns white on every pixel so that differences jump out at you;
252/// makes it easy to spot areas of difference that are in the least-significant
253/// bits.
254static inline SkPMColor compute_diff_white(SkPMColor c0, SkPMColor c1) {
255 return SkPackARGB32(0xFF, 0xFF, 0xFF, 0xFF);
256}
257
258static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1,
259 const int threshold) {
260 int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
261 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
262 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
263 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
264
265 return ((SkAbs32(da) <= threshold) &&
266 (SkAbs32(dr) <= threshold) &&
267 (SkAbs32(dg) <= threshold) &&
268 (SkAbs32(db) <= threshold));
269}
270
271// based on gm
272static void compute_diff(DiffRecord* dr,
273 DiffMetricProc diffFunction,
274 const int colorThreshold) {
275 SkAutoLockPixels alp(dr->fDifferenceBitmap);
276
277 const int w = dr->fComparisonBitmap.width();
278 const int h = dr->fComparisonBitmap.height();
279 int mismatchedPixels = 0;
280 int totalMismatchR = 0;
281 int totalMismatchG = 0;
282 int totalMismatchB = 0;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000283 // Accumulate fractionally different pixels, then divide out
284 // # of pixels at the end.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000285 dr->fWeightedFraction = 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000286 for (int y = 0; y < h; y++) {
287 for (int x = 0; x < w; x++) {
288 SkPMColor c0 = *dr->fBaseBitmap.getAddr32(x, y);
289 SkPMColor c1 = *dr->fComparisonBitmap.getAddr32(x, y);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000290 SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
291 SkPMColor outputDifference = diffFunction(c0, c1);
292 uint32_t thisR = SkGetPackedR32(trueDifference);
293 uint32_t thisG = SkGetPackedG32(trueDifference);
294 uint32_t thisB = SkGetPackedB32(trueDifference);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000295 totalMismatchR += thisR;
296 totalMismatchG += thisG;
297 totalMismatchB += thisB;
298 // In HSV, value is defined as max RGB component.
299 int value = MAX3(thisR, thisG, thisB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000300 dr->fWeightedFraction += ((float) value) / 255;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000301 if (thisR > dr->fMaxMismatchR) {
302 dr->fMaxMismatchR = thisR;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000303 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000304 if (thisG > dr->fMaxMismatchG) {
305 dr->fMaxMismatchG = thisG;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000306 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000307 if (thisB > dr->fMaxMismatchB) {
308 dr->fMaxMismatchB = thisB;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000309 }
310 if (!colors_match_thresholded(c0, c1, colorThreshold)) {
311 mismatchedPixels++;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000312 *dr->fDifferenceBitmap.getAddr32(x, y) = outputDifference;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000313 } else {
314 *dr->fDifferenceBitmap.getAddr32(x, y) = 0;
315 }
316 }
317 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000318 int pixelCount = w * h;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000319 dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
320 dr->fWeightedFraction /= pixelCount;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000321 dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
322 dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
323 dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000324}
325
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000326/// Given a image filename, returns the name of the file containing the
327/// associated difference image.
328static SkString filename_to_diff_filename (const SkString& filename) {
329 SkString diffName (filename);
330 const char* cstring = diffName.c_str();
331 int dotOffset = strrchr(cstring, '.') - cstring;
332 diffName.remove(dotOffset, diffName.size() - dotOffset);
333 diffName.append("-diff.png");
334 return diffName;
335}
336
tomhudson@google.com7d042802011-07-14 13:15:55 +0000337/// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo-actual.png"
338static SkString chrome_expected_path_to_actual (const SkString& expected) {
339 SkString actualPath (expected);
340 actualPath.remove(actualPath.size() - 13, 13);
341 actualPath.append("-actual.png");
342 return actualPath;
343}
344
345/// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo.png"
346static SkString chrome_expected_name_to_short (const SkString& expected) {
347 SkString shortName (expected);
348 shortName.remove(shortName.size() - 13, 13);
349 shortName.append(".png");
350 return shortName;
351}
352
353
354static void create_and_write_diff_image(DiffRecord* drp,
355 DiffMetricProc dmp,
356 const int colorThreshold,
357 const SkString& outputDir,
358 const SkString& filename) {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000359 const int w = drp->fBaseBitmap.width();
360 const int h = drp->fBaseBitmap.height();
361 drp->fDifferenceBitmap.setConfig(SkBitmap::kARGB_8888_Config, w, h);
362 drp->fDifferenceBitmap.allocPixels();
363 compute_diff(drp, dmp, colorThreshold);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000364
365 SkString outPath (outputDir);
366 outPath.append(filename_to_diff_filename(filename));
367 write_bitmap(outPath, drp->fDifferenceBitmap);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000368}
369
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000370/// Creates difference images, returns the number that have a 0 metric.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000371static void create_diff_images (DiffMetricProc dmp,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000372 const int colorThreshold,
373 RecordArray* differences,
374 const SkString& baseDir,
375 const SkString& comparisonDir,
376 const SkString& outputDir,
377 DiffSummary* summary) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000378
379 //@todo thudson 19 Apr 2011
380 // this lets us know about files in baseDir not in compareDir, but it
381 // doesn't detect files in compareDir not in baseDir. Doing that
382 // efficiently seems to imply iterating through both directories to
383 // create a merged list, and then attempting to process every entry
384 // in that list?
385
386 SkOSFile::Iter baseIterator (baseDir.c_str());
387 SkString filename;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000388 while (baseIterator.next(&filename)) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000389 if (filename.endsWith(".pdf")) {
390 continue;
391 }
tomhudson@google.com4e305982011-07-13 17:42:46 +0000392 SkString basePath (baseDir);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000393 basePath.append(filename);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000394 SkString comparisonPath (comparisonDir);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000395 comparisonPath.append(filename);
396 DiffRecord * drp = new DiffRecord (filename, basePath, comparisonPath);
397 if (!get_bitmaps(drp)) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000398 continue;
399 }
400
tomhudson@google.com7d042802011-07-14 13:15:55 +0000401 create_and_write_diff_image(drp, dmp, colorThreshold,
402 outputDir, filename);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000403
404 differences->push(drp);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000405 summary->add(drp);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000406 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000407}
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000408
tomhudson@google.com7d042802011-07-14 13:15:55 +0000409static void create_diff_images_chromium (DiffMetricProc dmp,
410 const int colorThreshold,
411 RecordArray* differences,
412 const SkString& dirname,
413 const SkString& outputDir,
414 DiffSummary* summary) {
415 SkOSFile::Iter baseIterator (dirname.c_str());
416 SkString filename;
417 while (baseIterator.next(&filename)) {
418 if (filename.endsWith(".pdf")) {
419 continue;
420 }
421 if (filename.endsWith("-expected.png")) {
422 SkString expectedPath (dirname);
423 expectedPath.append(filename);
424 SkString shortName (chrome_expected_name_to_short(filename));
425 SkString actualPath (chrome_expected_path_to_actual(expectedPath));
426 DiffRecord * drp =
427 new DiffRecord (shortName, expectedPath, actualPath);
428 if (!get_bitmaps(drp)) {
429 continue;
430 }
431 create_and_write_diff_image(drp, dmp, colorThreshold,
432 outputDir, shortName);
433
434 differences->push(drp);
435 summary->add(drp);
436 }
437 }
438}
439
440static void analyze_chromium(DiffMetricProc dmp,
441 const int colorThreshold,
442 RecordArray* differences,
443 const SkString& dirname,
444 const SkString& outputDir,
445 DiffSummary* summary) {
446 create_diff_images_chromium(dmp, colorThreshold, differences,
447 dirname, outputDir, summary);
448 SkOSFile::Iter dirIterator(dirname.c_str());
449 SkString newdirname;
450 while (dirIterator.next(&newdirname, true)) {
451 if (newdirname.startsWith(".")) {
452 continue;
453 }
454 SkString fullname (dirname);
455 fullname.append(newdirname);
456 if (!fullname.endsWith("/")) {
457 fullname.append("/");
458 }
459 analyze_chromium(dmp, colorThreshold, differences,
460 fullname, outputDir, summary);
461 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000462}
463
464/// Make layout more consistent by scaling image to 240 height, 360 width,
465/// or natural size, whichever is smallest.
466static int compute_image_height (const SkBitmap& bmp) {
467 int retval = 240;
468 if (bmp.height() < retval) {
469 retval = bmp.height();
470 }
471 float scale = (float) retval / bmp.height();
472 if (bmp.width() * scale > 360) {
473 scale = (float) 360 / bmp.width();
474 retval = bmp.height() * scale;
475 }
476 return retval;
477}
478
479static void print_page_header (SkFILEWStream* stream,
480 const int matchCount,
481 const int colorThreshold,
482 const RecordArray& differences) {
483 SkTime::DateTime dt;
484 SkTime::GetDateTime(&dt);
485 stream->writeText("SkDiff run at ");
486 stream->writeDecAsText(dt.fHour);
487 stream->writeText(":");
488 if (dt.fMinute < 10) {
489 stream->writeText("0");
490 }
491 stream->writeDecAsText(dt.fMinute);
492 stream->writeText(":");
493 if (dt.fSecond < 10) {
494 stream->writeText("0");
495 }
496 stream->writeDecAsText(dt.fSecond);
497 stream->writeText("<br>");
498 stream->writeDecAsText(matchCount);
499 stream->writeText(" of ");
500 stream->writeDecAsText(differences.count());
501 stream->writeText(" images matched ");
502 if (colorThreshold == 0) {
503 stream->writeText("exactly");
504 } else {
505 stream->writeText("within ");
506 stream->writeDecAsText(colorThreshold);
507 stream->writeText(" color units per component");
508 }
509 stream->writeText(".<br>");
510
511}
512
513static void print_pixel_count (SkFILEWStream* stream,
514 const DiffRecord& diff) {
515 stream->writeText("<br>(");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000516 stream->writeDecAsText(diff.fFractionDifference *
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000517 diff.fBaseBitmap.width() *
518 diff.fBaseBitmap.height());
519 stream->writeText(" pixels)");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000520/*
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000521 stream->writeDecAsText(diff.fWeightedFraction *
tomhudson@google.com5b325292011-05-24 19:41:13 +0000522 diff.fBaseBitmap.width() *
523 diff.fBaseBitmap.height());
524 stream->writeText(" weighted pixels)");
525*/
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000526}
527
528static void print_label_cell (SkFILEWStream* stream,
529 const DiffRecord& diff) {
530 stream->writeText("<td>");
531 stream->writeText(diff.fFilename.c_str());
532 stream->writeText("<br>");
533 char metricBuf [20];
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000534 sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000535 stream->writeText(metricBuf);
536 stream->writeText(" of pixels differ");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000537 stream->writeText("\n (");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000538 sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000539 stream->writeText(metricBuf);
540 stream->writeText(" weighted)");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000541 // Write the actual number of pixels that differ if it's < 1%
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000542 if (diff.fFractionDifference < 0.01) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000543 print_pixel_count(stream, diff);
544 }
545 stream->writeText("<br>Average color mismatch ");
546 stream->writeDecAsText(MAX3(diff.fAverageMismatchR,
547 diff.fAverageMismatchG,
548 diff.fAverageMismatchB));
549 stream->writeText("<br>Max color mismatch ");
550 stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
551 diff.fMaxMismatchG,
552 diff.fMaxMismatchB));
553 stream->writeText("</td>");
554}
555
556static void print_image_cell (SkFILEWStream* stream,
tomhudson@google.com4e305982011-07-13 17:42:46 +0000557 const SkString& path,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000558 int height) {
559 stream->writeText("<td><a href=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000560 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000561 stream->writeText("\"><img src=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000562 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000563 stream->writeText("\" height=\"");
564 stream->writeDecAsText(height);
565 stream->writeText("px\"></a></td>");
566}
567
568static void print_diff_page (const int matchCount,
569 const int colorThreshold,
570 const RecordArray& differences,
571 const SkString& baseDir,
572 const SkString& comparisonDir,
573 const SkString& outputDir) {
574
tomhudson@google.com5b325292011-05-24 19:41:13 +0000575 SkString outputPath (outputDir);
576 outputPath.append("index.html");
577 //SkFILEWStream outputStream ("index.html");
578 SkFILEWStream outputStream (outputPath.c_str());
579
tomhudson@google.com4e305982011-07-13 17:42:46 +0000580 // Need to convert paths from relative-to-cwd to relative-to-outputDir
tomhudson@google.com5b325292011-05-24 19:41:13 +0000581 // FIXME this doesn't work if there are '..' inside the outputDir
582 unsigned int ui;
583 SkString relativePath;
584 for (ui = 0; ui < outputDir.size(); ui++) {
585 if (outputDir[ui] == '/') {
586 relativePath.append("../");
587 }
588 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000589
590 outputStream.writeText("<html>\n<body>\n");
591 print_page_header(&outputStream, matchCount, colorThreshold, differences);
592
593 outputStream.writeText("<table>\n");
594 int i;
595 for (i = 0; i < differences.count(); i++) {
596 DiffRecord* diff = differences[i];
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000597 if (0 == diff->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000598 continue;
599 }
tomhudson@google.com4e305982011-07-13 17:42:46 +0000600 if (!diff->fBasePath.startsWith("/")) {
601 diff->fBasePath.prepend(relativePath);
602 }
603 if (!diff->fComparisonPath.startsWith("/")) {
604 diff->fComparisonPath.prepend(relativePath);
605 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000606 int height = compute_image_height(diff->fBaseBitmap);
607 outputStream.writeText("<tr>\n");
608 print_label_cell(&outputStream, *diff);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000609 print_image_cell(&outputStream, diff->fBasePath, height);
610 print_image_cell(&outputStream,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000611 filename_to_diff_filename(diff->fFilename), height);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000612 print_image_cell(&outputStream, diff->fComparisonPath, height);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000613 outputStream.writeText("</tr>\n");
614 outputStream.flush();
615 }
616 outputStream.writeText("</table>\n");
617 outputStream.writeText("</body>\n</html>\n");
618 outputStream.flush();
619}
620
621static void usage (char * argv0) {
622 SkDebugf("Skia baseline image diff tool\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000623 SkDebugf("Usage: %s baseDir comparisonDir [outputDir]\n", argv0);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000624 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000625" %s --chromium --release|--debug baseDir outputDir\n", argv0);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000626 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000627" --white: force all difference pixels to white\n"
628" --threshold n: only report differences > n (in one channel) [default 0]\n"
629" --sortbymismatch: sort by average color channel mismatch\n");
630 SkDebugf(
631" --sortbymaxmismatch: sort by worst color channel mismatch,\n"
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000632" break ties with -sortbymismatch,\n"
633" [default by fraction of pixels mismatching]\n");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000634 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000635" --weighted: sort by # pixels different weighted by color difference\n");
636 SkDebugf(
637" --chromium-release: process Webkit LayoutTests results instead of gm\n"
638" --chromium-debug: process Webkit LayoutTests results instead of gm\n");
639 SkDebugf(
640" baseDir: directory to read baseline images from,\n"
641" or chromium/src directory for --chromium.\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000642 SkDebugf(" comparisonDir: directory to read comparison images from\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000643 SkDebugf(
644" outputDir: directory to write difference images to; defaults to\n"
tomhudson@google.com7d042802011-07-14 13:15:55 +0000645" comparisonDir when not running --chromium\n");
646 SkDebugf("Also creates an \"index.html\" file in the output directory.\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000647}
648
649int main (int argc, char ** argv) {
650 DiffMetricProc diffProc = compute_diff_pmcolor;
651 SkQSortCompareProc sortProc = (SkQSortCompareProc) compare_diff_metrics;
652
653 // Maximum error tolerated in any one color channel in any one pixel before
654 // a difference is reported.
655 int colorThreshold = 0;
656 SkString baseDir;
657 SkString comparisonDir;
658 SkString outputDir;
659
tomhudson@google.com7d042802011-07-14 13:15:55 +0000660 bool analyzeChromium = false;
661 bool chromiumDebug = false;
662 bool chromiumRelease = false;
663
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000664 RecordArray differences;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000665 DiffSummary summary;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000666
667 int i, j;
668 for (i = 1, j = 0; i < argc; i++) {
tomhudson@google.com7d042802011-07-14 13:15:55 +0000669 if (!strcmp(argv[i], "--help")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000670 usage(argv[0]);
671 return 0;
672 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000673 if (!strcmp(argv[i], "--white")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000674 diffProc = compute_diff_white;
675 continue;
676 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000677 if (!strcmp(argv[i], "--sortbymismatch")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000678 sortProc = (SkQSortCompareProc) compare_diff_mean_mismatches;
679 continue;
680 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000681 if (!strcmp(argv[i], "--sortbymaxmismatch")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000682 sortProc = (SkQSortCompareProc) compare_diff_max_mismatches;
683 continue;
684 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000685 if (!strcmp(argv[i], "--weighted")) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000686 sortProc = (SkQSortCompareProc) compare_diff_weighted;
687 continue;
688 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000689 if (!strcmp(argv[i], "--chromium-release")) {
690 analyzeChromium = true;
691 chromiumRelease = true;
692 continue;
693 }
694 if (!strcmp(argv[i], "--chromium-debug")) {
695 analyzeChromium = true;
696 chromiumDebug = true;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000697 continue;
698 }
699 if (argv[i][0] != '-') {
700 switch (j++) {
701 case 0:
702 baseDir.set(argv[i]);
703 continue;
704 case 1:
705 comparisonDir.set(argv[i]);
706 continue;
707 case 2:
708 outputDir.set(argv[i]);
709 continue;
710 default:
711 usage(argv[0]);
712 return 0;
713 }
714 }
715
716 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
717 usage(argv[0]);
718 return 0;
719 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000720 if (analyzeChromium) {
721 if (j != 2) {
722 usage(argv[0]);
723 return 0;
724 }
725 if (chromiumRelease && chromiumDebug) {
726 SkDebugf(
727"--chromium must be either -release or -debug, not both!\n");
728 return 0;
729 }
730 }
731
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000732 if (j == 2) {
733 outputDir = comparisonDir;
734 } else if (j != 3) {
735 usage(argv[0]);
736 return 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000737 }
738
739 if (!baseDir.endsWith("/")) {
740 baseDir.append("/");
741 }
742 if (!comparisonDir.endsWith("/")) {
743 comparisonDir.append("/");
744 }
745 if (!outputDir.endsWith("/")) {
746 outputDir.append("/");
747 }
748
tomhudson@google.com7d042802011-07-14 13:15:55 +0000749 if (analyzeChromium) {
750 baseDir.append("webkit/");
751 if (chromiumRelease) {
752 baseDir.append("Release/");
753 }
754 if (chromiumDebug) {
755 baseDir.append("Debug/");
756 }
757 baseDir.append("layout-test-results/");
758 analyze_chromium(diffProc, colorThreshold, &differences,
759 baseDir, outputDir, &summary);
760 } else {
761 create_diff_images(diffProc, colorThreshold, &differences,
762 baseDir, comparisonDir, outputDir, &summary);
763 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000764 summary.print();
tomhudson@google.com7d042802011-07-14 13:15:55 +0000765
766 if (differences.count()) {
767 SkQSort(differences.begin(), differences.count(),
768 sizeof(DiffRecord*), sortProc);
769 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000770 print_diff_page(summary.fNumMatches, colorThreshold, differences,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000771 baseDir, comparisonDir, outputDir);
772
773 for (i = 0; i < differences.count(); i++) {
774 delete differences[i];
775 }
776}