blob: d9bcc592008bfb67f0c5d049d35b454d6ff036b1 [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.
19 * Creates an index.html in the current working directory to compare each
20 * pair that does not match exactly.
21 * Does *not* recursively descend directories.
22 */
23
24struct DiffRecord {
25 DiffRecord (const SkString filename)
26 : fFilename (filename)
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000027 , fFractionDifference (0)
28 , fWeightedFraction (0)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000029 , fAverageMismatchR (0)
30 , fAverageMismatchG (0)
31 , fAverageMismatchB (0)
32 , fMaxMismatchR (0)
33 , fMaxMismatchG (0)
34 , fMaxMismatchB (0) { };
35
36 SkString fFilename;
37
38 SkBitmap fBaseBitmap;
39 SkBitmap fComparisonBitmap;
40 SkBitmap fDifferenceBitmap;
41
42 /// Arbitrary floating-point metric to be used to sort images from most
43 /// to least different from baseline; values of 0 will be omitted from the
44 /// summary webpage.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000045 float fFractionDifference;
46 float fWeightedFraction;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000047
48 float fAverageMismatchR;
49 float fAverageMismatchG;
50 float fAverageMismatchB;
51
52 uint32_t fMaxMismatchR;
53 uint32_t fMaxMismatchG;
54 uint32_t fMaxMismatchB;
55};
56
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000057#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
58#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
59
60struct DiffSummary {
61 DiffSummary ()
62 : fNumMatches (0)
63 , fNumMismatches (0)
64 , fMaxMismatchV (0)
65 , fMaxMismatchPercent (0) { };
66
67 uint32_t fNumMatches;
68 uint32_t fNumMismatches;
69 uint32_t fMaxMismatchV;
70 float fMaxMismatchPercent;
71
72 void print () {
73 printf("%d of %d images matched.\n", fNumMatches,
74 fNumMatches + fNumMismatches);
75 if (fNumMismatches > 0) {
76 printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
77 printf("Largest area mismatch was %.2f%% of pixels\n",
78 fMaxMismatchPercent);
79 }
80
81 }
82
83 void add (DiffRecord* drp) {
84 if (0 == drp->fFractionDifference) {
85 fNumMatches++;
86 } else {
87 fNumMismatches++;
88 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
89 fMaxMismatchPercent = drp->fFractionDifference * 100;
90 }
tomhudson@google.com88a0e052011-06-09 18:54:01 +000091 uint32_t value = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
92 drp->fMaxMismatchB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000093 if (value > fMaxMismatchV) {
94 fMaxMismatchV = value;
95 }
96 }
97 }
98};
99
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000100typedef SkTDArray<DiffRecord*> RecordArray;
101
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000102/// Comparison routine for qsort; sorts by fFractionDifference
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000103/// from largest to smallest.
104static int compare_diff_metrics (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000105 if ((*lhs)->fFractionDifference < (*rhs)->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000106 return 1;
107 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000108 if ((*rhs)->fFractionDifference < (*lhs)->fFractionDifference) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000109 return -1;
110 }
111 return 0;
112}
113
114static int compare_diff_weighted (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000115 if ((*lhs)->fWeightedFraction < (*rhs)->fWeightedFraction) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000116 return 1;
117 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000118 if ((*lhs)->fWeightedFraction > (*rhs)->fWeightedFraction) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000119 return -1;
120 }
121 return 0;
122}
123
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000124/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
125/// from largest to smallest.
126static int compare_diff_mean_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
127 float leftValue = MAX3((*lhs)->fAverageMismatchR,
128 (*lhs)->fAverageMismatchG,
129 (*lhs)->fAverageMismatchB);
130 float rightValue = MAX3((*rhs)->fAverageMismatchR,
131 (*rhs)->fAverageMismatchG,
132 (*rhs)->fAverageMismatchB);
133 if (leftValue < rightValue) {
134 return 1;
135 }
136 if (rightValue < leftValue) {
137 return -1;
138 }
139 return 0;
140}
141
142/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB})
143/// from largest to smallest.
144static int compare_diff_max_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
145 float leftValue = MAX3((*lhs)->fMaxMismatchR,
146 (*lhs)->fMaxMismatchG,
147 (*lhs)->fMaxMismatchB);
148 float rightValue = MAX3((*rhs)->fMaxMismatchR,
149 (*rhs)->fMaxMismatchG,
150 (*rhs)->fMaxMismatchB);
151 if (leftValue < rightValue) {
152 return 1;
153 }
154 if (rightValue < leftValue) {
155 return -1;
156 }
157 return compare_diff_mean_mismatches(lhs, rhs);
158}
159
160
161
162/// Parameterized routine to compute the color of a pixel in a difference image.
163typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
164
165static bool get_bitmaps (DiffRecord* diffRecord,
166 const SkString& baseDir,
167 const SkString& comparisonDir) {
168 SkString comparePath (comparisonDir);
169 comparePath.append(diffRecord->fFilename);
170 SkFILEStream compareStream(comparePath.c_str());
171 if (!compareStream.isValid()) {
172 SkDebugf("WARNING: couldn't open comparison file <%s>\n",
173 comparePath.c_str());
174 return false;
175 }
176
177 SkString basePath (baseDir);
178 basePath.append(diffRecord->fFilename);
179 SkFILEStream baseStream(basePath.c_str());
180 if (!baseStream.isValid()) {
181 SkDebugf("ERROR: couldn't open base file <%s>\n",
182 basePath.c_str());
183 return false;
184 }
185
186 SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
187 if (NULL == codec) {
188 SkDebugf("ERROR: no codec found for <%s>\n",
189 basePath.c_str());
190 return false;
191 }
192
193 SkAutoTDelete<SkImageDecoder> ad(codec);
194
195 baseStream.rewind();
196 if (!codec->decode(&baseStream, &diffRecord->fBaseBitmap,
197 SkBitmap::kARGB_8888_Config,
198 SkImageDecoder::kDecodePixels_Mode)) {
199 SkDebugf("ERROR: codec failed for <%s>\n",
200 basePath.c_str());
201 return false;
202 }
203
204 if (!codec->decode(&compareStream, &diffRecord->fComparisonBitmap,
205 SkBitmap::kARGB_8888_Config,
206 SkImageDecoder::kDecodePixels_Mode)) {
207 SkDebugf("ERROR: codec failed for <%s>\n",
208 comparePath.c_str());
209 return false;
210 }
211
212 return true;
213}
214
215// from gm - thanks to PNG, we need to force all pixels 100% opaque
216static void force_all_opaque(const SkBitmap& bitmap) {
217 SkAutoLockPixels lock(bitmap);
218 for (int y = 0; y < bitmap.height(); y++) {
219 for (int x = 0; x < bitmap.width(); x++) {
220 *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
221 }
222 }
223}
224
225// from gm
226static bool write_bitmap(const SkString& path, const SkBitmap& bitmap) {
227 SkBitmap copy;
228 bitmap.copyTo(&copy, SkBitmap::kARGB_8888_Config);
229 force_all_opaque(copy);
230 return SkImageEncoder::EncodeFile(path.c_str(), copy,
231 SkImageEncoder::kPNG_Type, 100);
232}
233
234// from gm
235static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
236 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
237 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
238 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
239
240 return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
241}
242
243/// Returns white on every pixel so that differences jump out at you;
244/// makes it easy to spot areas of difference that are in the least-significant
245/// bits.
246static inline SkPMColor compute_diff_white(SkPMColor c0, SkPMColor c1) {
247 return SkPackARGB32(0xFF, 0xFF, 0xFF, 0xFF);
248}
249
250static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1,
251 const int threshold) {
252 int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
253 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
254 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
255 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
256
257 return ((SkAbs32(da) <= threshold) &&
258 (SkAbs32(dr) <= threshold) &&
259 (SkAbs32(dg) <= threshold) &&
260 (SkAbs32(db) <= threshold));
261}
262
263// based on gm
264static void compute_diff(DiffRecord* dr,
265 DiffMetricProc diffFunction,
266 const int colorThreshold) {
267 SkAutoLockPixels alp(dr->fDifferenceBitmap);
268
269 const int w = dr->fComparisonBitmap.width();
270 const int h = dr->fComparisonBitmap.height();
271 int mismatchedPixels = 0;
272 int totalMismatchR = 0;
273 int totalMismatchG = 0;
274 int totalMismatchB = 0;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000275 // Accumulate fractionally different pixels, then divide out
276 // # of pixels at the end.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000277 dr->fWeightedFraction = 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000278 for (int y = 0; y < h; y++) {
279 for (int x = 0; x < w; x++) {
280 SkPMColor c0 = *dr->fBaseBitmap.getAddr32(x, y);
281 SkPMColor c1 = *dr->fComparisonBitmap.getAddr32(x, y);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000282 SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
283 SkPMColor outputDifference = diffFunction(c0, c1);
284 uint32_t thisR = SkGetPackedR32(trueDifference);
285 uint32_t thisG = SkGetPackedG32(trueDifference);
286 uint32_t thisB = SkGetPackedB32(trueDifference);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000287 totalMismatchR += thisR;
288 totalMismatchG += thisG;
289 totalMismatchB += thisB;
290 // In HSV, value is defined as max RGB component.
291 int value = MAX3(thisR, thisG, thisB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000292 dr->fWeightedFraction += ((float) value) / 255;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000293 if (thisR > dr->fMaxMismatchR) {
294 dr->fMaxMismatchR = thisR;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000295 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000296 if (thisG > dr->fMaxMismatchG) {
297 dr->fMaxMismatchG = thisG;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000298 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000299 if (thisB > dr->fMaxMismatchB) {
300 dr->fMaxMismatchB = thisB;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000301 }
302 if (!colors_match_thresholded(c0, c1, colorThreshold)) {
303 mismatchedPixels++;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000304 *dr->fDifferenceBitmap.getAddr32(x, y) = outputDifference;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000305 } else {
306 *dr->fDifferenceBitmap.getAddr32(x, y) = 0;
307 }
308 }
309 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000310 int pixelCount = w * h;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000311 dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
312 dr->fWeightedFraction /= pixelCount;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000313 dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
314 dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
315 dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000316}
317
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000318/// Given a image filename, returns the name of the file containing the
319/// associated difference image.
320static SkString filename_to_diff_filename (const SkString& filename) {
321 SkString diffName (filename);
322 const char* cstring = diffName.c_str();
323 int dotOffset = strrchr(cstring, '.') - cstring;
324 diffName.remove(dotOffset, diffName.size() - dotOffset);
325 diffName.append("-diff.png");
326 return diffName;
327}
328
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000329/// Creates difference images, returns the number that have a 0 metric.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000330static void create_diff_images (DiffMetricProc dmp,
331 SkQSortCompareProc scp,
332 const int colorThreshold,
333 RecordArray* differences,
334 const SkString& baseDir,
335 const SkString& comparisonDir,
336 const SkString& outputDir,
337 DiffSummary* summary) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000338
339 //@todo thudson 19 Apr 2011
340 // this lets us know about files in baseDir not in compareDir, but it
341 // doesn't detect files in compareDir not in baseDir. Doing that
342 // efficiently seems to imply iterating through both directories to
343 // create a merged list, and then attempting to process every entry
344 // in that list?
345
346 SkOSFile::Iter baseIterator (baseDir.c_str());
347 SkString filename;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000348 while (baseIterator.next(&filename)) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000349 if (filename.endsWith(".pdf")) {
350 continue;
351 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000352 DiffRecord * drp = new DiffRecord (filename);
353 if (!get_bitmaps(drp, baseDir, comparisonDir)) {
354 continue;
355 }
356
357 const int w = drp->fBaseBitmap.width();
358 const int h = drp->fBaseBitmap.height();
359 drp->fDifferenceBitmap.setConfig(SkBitmap::kARGB_8888_Config, w, h);
360 drp->fDifferenceBitmap.allocPixels();
361 compute_diff(drp, dmp, colorThreshold);
362
363 SkString outPath (outputDir);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000364 outPath.append(filename_to_diff_filename(filename));
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000365 write_bitmap(outPath, drp->fDifferenceBitmap);
366
367 differences->push(drp);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000368 summary->add(drp);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000369 }
370
371 SkQSort(differences->begin(), differences->count(), sizeof(DiffRecord*),
372 scp);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000373}
374
375/// Make layout more consistent by scaling image to 240 height, 360 width,
376/// or natural size, whichever is smallest.
377static int compute_image_height (const SkBitmap& bmp) {
378 int retval = 240;
379 if (bmp.height() < retval) {
380 retval = bmp.height();
381 }
382 float scale = (float) retval / bmp.height();
383 if (bmp.width() * scale > 360) {
384 scale = (float) 360 / bmp.width();
385 retval = bmp.height() * scale;
386 }
387 return retval;
388}
389
390static void print_page_header (SkFILEWStream* stream,
391 const int matchCount,
392 const int colorThreshold,
393 const RecordArray& differences) {
394 SkTime::DateTime dt;
395 SkTime::GetDateTime(&dt);
396 stream->writeText("SkDiff run at ");
397 stream->writeDecAsText(dt.fHour);
398 stream->writeText(":");
399 if (dt.fMinute < 10) {
400 stream->writeText("0");
401 }
402 stream->writeDecAsText(dt.fMinute);
403 stream->writeText(":");
404 if (dt.fSecond < 10) {
405 stream->writeText("0");
406 }
407 stream->writeDecAsText(dt.fSecond);
408 stream->writeText("<br>");
409 stream->writeDecAsText(matchCount);
410 stream->writeText(" of ");
411 stream->writeDecAsText(differences.count());
412 stream->writeText(" images matched ");
413 if (colorThreshold == 0) {
414 stream->writeText("exactly");
415 } else {
416 stream->writeText("within ");
417 stream->writeDecAsText(colorThreshold);
418 stream->writeText(" color units per component");
419 }
420 stream->writeText(".<br>");
421
422}
423
424static void print_pixel_count (SkFILEWStream* stream,
425 const DiffRecord& diff) {
426 stream->writeText("<br>(");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000427 stream->writeDecAsText(diff.fFractionDifference *
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000428 diff.fBaseBitmap.width() *
429 diff.fBaseBitmap.height());
430 stream->writeText(" pixels)");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000431/*
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000432 stream->writeDecAsText(diff.fWeightedFraction *
tomhudson@google.com5b325292011-05-24 19:41:13 +0000433 diff.fBaseBitmap.width() *
434 diff.fBaseBitmap.height());
435 stream->writeText(" weighted pixels)");
436*/
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000437}
438
439static void print_label_cell (SkFILEWStream* stream,
440 const DiffRecord& diff) {
441 stream->writeText("<td>");
442 stream->writeText(diff.fFilename.c_str());
443 stream->writeText("<br>");
444 char metricBuf [20];
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000445 sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000446 stream->writeText(metricBuf);
447 stream->writeText(" of pixels differ");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000448 stream->writeText("\n (");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000449 sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000450 stream->writeText(metricBuf);
451 stream->writeText(" weighted)");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000452 // Write the actual number of pixels that differ if it's < 1%
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000453 if (diff.fFractionDifference < 0.01) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000454 print_pixel_count(stream, diff);
455 }
456 stream->writeText("<br>Average color mismatch ");
457 stream->writeDecAsText(MAX3(diff.fAverageMismatchR,
458 diff.fAverageMismatchG,
459 diff.fAverageMismatchB));
460 stream->writeText("<br>Max color mismatch ");
461 stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
462 diff.fMaxMismatchG,
463 diff.fMaxMismatchB));
464 stream->writeText("</td>");
465}
466
467static void print_image_cell (SkFILEWStream* stream,
468 const SkString& directory,
469 const SkString& filename,
470 int height) {
471 stream->writeText("<td><a href=\"");
472 stream->writeText(directory.c_str());
473 stream->writeText(filename.c_str());
474 stream->writeText("\"><img src=\"");
475 stream->writeText(directory.c_str());
476 stream->writeText(filename.c_str());
477 stream->writeText("\" height=\"");
478 stream->writeDecAsText(height);
479 stream->writeText("px\"></a></td>");
480}
481
482static void print_diff_page (const int matchCount,
483 const int colorThreshold,
484 const RecordArray& differences,
485 const SkString& baseDir,
486 const SkString& comparisonDir,
487 const SkString& outputDir) {
488
tomhudson@google.com5b325292011-05-24 19:41:13 +0000489 const SkString localDir ("");
490 SkString outputPath (outputDir);
491 outputPath.append("index.html");
492 //SkFILEWStream outputStream ("index.html");
493 SkFILEWStream outputStream (outputPath.c_str());
494
495 // FIXME this doesn't work if there are '..' inside the outputDir
496 unsigned int ui;
497 SkString relativePath;
498 for (ui = 0; ui < outputDir.size(); ui++) {
499 if (outputDir[ui] == '/') {
500 relativePath.append("../");
501 }
502 }
503 SkString relativeBaseDir (relativePath);
504 SkString relativeComparisonDir (relativePath);
505 relativeBaseDir.append(baseDir);
506 relativeComparisonDir.append(comparisonDir);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000507
508 outputStream.writeText("<html>\n<body>\n");
509 print_page_header(&outputStream, matchCount, colorThreshold, differences);
510
511 outputStream.writeText("<table>\n");
512 int i;
513 for (i = 0; i < differences.count(); i++) {
514 DiffRecord* diff = differences[i];
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000515 if (0 == diff->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000516 continue;
517 }
518 int height = compute_image_height(diff->fBaseBitmap);
519 outputStream.writeText("<tr>\n");
520 print_label_cell(&outputStream, *diff);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000521 print_image_cell(&outputStream, relativeBaseDir,
522 diff->fFilename, height);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000523 print_image_cell(&outputStream, localDir,
524 filename_to_diff_filename(diff->fFilename), height);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000525 print_image_cell(&outputStream, relativeComparisonDir,
526 diff->fFilename, height);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000527 outputStream.writeText("</tr>\n");
528 outputStream.flush();
529 }
530 outputStream.writeText("</table>\n");
531 outputStream.writeText("</body>\n</html>\n");
532 outputStream.flush();
533}
534
535static void usage (char * argv0) {
536 SkDebugf("Skia baseline image diff tool\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000537 SkDebugf("Usage: %s baseDir comparisonDir [outputDir]\n", argv0);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000538 SkDebugf(
539" -white: force all difference pixels to white\n"
540" -threshold n: only report differences > n (in one channel) [default 0]\n"
541" -sortbymismatch: sort by average color channel mismatch\n");
542 SkDebugf(
543" -sortbymaxmismatch: sort by worst color channel mismatch,\n"
544" break ties with -sortbymismatch,\n"
545" [default by fraction of pixels mismatching]\n");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000546 SkDebugf(
547" -weighted: sort by # pixels different weighted by color difference\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000548 SkDebugf(" baseDir: directory to read baseline images from\n");
549 SkDebugf(" comparisonDir: directory to read comparison images from\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000550 SkDebugf(
551" outputDir: directory to write difference images to; defaults to\n"
552" comparisonDir\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000553 SkDebugf("Also creates an \"index.html\" file in the current directory.\n");
554}
555
556int main (int argc, char ** argv) {
557 DiffMetricProc diffProc = compute_diff_pmcolor;
558 SkQSortCompareProc sortProc = (SkQSortCompareProc) compare_diff_metrics;
559
560 // Maximum error tolerated in any one color channel in any one pixel before
561 // a difference is reported.
562 int colorThreshold = 0;
563 SkString baseDir;
564 SkString comparisonDir;
565 SkString outputDir;
566
567 RecordArray differences;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000568 DiffSummary summary;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000569
570 int i, j;
571 for (i = 1, j = 0; i < argc; i++) {
572 if (!strcmp(argv[i], "-help")) {
573 usage(argv[0]);
574 return 0;
575 }
576 if (!strcmp(argv[i], "-white")) {
577 diffProc = compute_diff_white;
578 continue;
579 }
580 if (!strcmp(argv[i], "-sortbymismatch")) {
581 sortProc = (SkQSortCompareProc) compare_diff_mean_mismatches;
582 continue;
583 }
584 if (!strcmp(argv[i], "-sortbymaxmismatch")) {
585 sortProc = (SkQSortCompareProc) compare_diff_max_mismatches;
586 continue;
587 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000588 if (!strcmp(argv[i], "-weighted")) {
589 sortProc = (SkQSortCompareProc) compare_diff_weighted;
590 continue;
591 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000592 if (!strcmp(argv[i], "-threshold")) {
593 colorThreshold = atoi(argv[++i]);
594 continue;
595 }
596 if (argv[i][0] != '-') {
597 switch (j++) {
598 case 0:
599 baseDir.set(argv[i]);
600 continue;
601 case 1:
602 comparisonDir.set(argv[i]);
603 continue;
604 case 2:
605 outputDir.set(argv[i]);
606 continue;
607 default:
608 usage(argv[0]);
609 return 0;
610 }
611 }
612
613 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
614 usage(argv[0]);
615 return 0;
616 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000617 if (j == 2) {
618 outputDir = comparisonDir;
619 } else if (j != 3) {
620 usage(argv[0]);
621 return 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000622 }
623
624 if (!baseDir.endsWith("/")) {
625 baseDir.append("/");
626 }
627 if (!comparisonDir.endsWith("/")) {
628 comparisonDir.append("/");
629 }
630 if (!outputDir.endsWith("/")) {
631 outputDir.append("/");
632 }
633
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000634 create_diff_images(diffProc, sortProc, colorThreshold, &differences,
635 baseDir, comparisonDir, outputDir, &summary);
636 summary.print();
637 print_diff_page(summary.fNumMatches, colorThreshold, differences,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000638 baseDir, comparisonDir, outputDir);
639
640 for (i = 0; i < differences.count(); i++) {
641 delete differences[i];
642 }
643}