blob: 090a2a54bc0da5edf2a65157a986422da20d6d83 [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 {
tomhudson@google.com4e305982011-07-13 17:42:46 +000025 DiffRecord (const SkString filename,
26 const SkString basePath,
27 const SkString comparisonPath)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000028 : fFilename (filename)
tomhudson@google.com4e305982011-07-13 17:42:46 +000029 , fBasePath (basePath)
30 , fComparisonPath (comparisonPath)
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000031 , fFractionDifference (0)
32 , fWeightedFraction (0)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000033 , fAverageMismatchR (0)
34 , fAverageMismatchG (0)
35 , fAverageMismatchB (0)
36 , fMaxMismatchR (0)
37 , fMaxMismatchG (0)
tomhudson@google.com4e305982011-07-13 17:42:46 +000038 , fMaxMismatchB (0) {
39 SkASSERT(basePath.endsWith(filename.c_str()));
40 SkASSERT(comparisonPath.endsWith(filename.c_str()));
41 };
tomhudson@google.com4b33d282011-04-27 15:39:30 +000042
43 SkString fFilename;
tomhudson@google.com4e305982011-07-13 17:42:46 +000044 SkString fBasePath;
45 SkString fComparisonPath;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000046
47 SkBitmap fBaseBitmap;
48 SkBitmap fComparisonBitmap;
49 SkBitmap fDifferenceBitmap;
50
51 /// Arbitrary floating-point metric to be used to sort images from most
52 /// to least different from baseline; values of 0 will be omitted from the
53 /// summary webpage.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000054 float fFractionDifference;
55 float fWeightedFraction;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000056
57 float fAverageMismatchR;
58 float fAverageMismatchG;
59 float fAverageMismatchB;
60
61 uint32_t fMaxMismatchR;
62 uint32_t fMaxMismatchG;
63 uint32_t fMaxMismatchB;
64};
65
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000066#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
67#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
68
69struct DiffSummary {
70 DiffSummary ()
71 : fNumMatches (0)
72 , fNumMismatches (0)
73 , fMaxMismatchV (0)
74 , fMaxMismatchPercent (0) { };
75
76 uint32_t fNumMatches;
77 uint32_t fNumMismatches;
78 uint32_t fMaxMismatchV;
79 float fMaxMismatchPercent;
80
81 void print () {
82 printf("%d of %d images matched.\n", fNumMatches,
83 fNumMatches + fNumMismatches);
84 if (fNumMismatches > 0) {
85 printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
86 printf("Largest area mismatch was %.2f%% of pixels\n",
87 fMaxMismatchPercent);
88 }
89
90 }
91
92 void add (DiffRecord* drp) {
93 if (0 == drp->fFractionDifference) {
94 fNumMatches++;
95 } else {
96 fNumMismatches++;
97 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
98 fMaxMismatchPercent = drp->fFractionDifference * 100;
99 }
tomhudson@google.com88a0e052011-06-09 18:54:01 +0000100 uint32_t value = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
101 drp->fMaxMismatchB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000102 if (value > fMaxMismatchV) {
103 fMaxMismatchV = value;
104 }
105 }
106 }
107};
108
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000109typedef SkTDArray<DiffRecord*> RecordArray;
110
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000111/// Comparison routine for qsort; sorts by fFractionDifference
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000112/// from largest to smallest.
113static int compare_diff_metrics (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000114 if ((*lhs)->fFractionDifference < (*rhs)->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000115 return 1;
116 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000117 if ((*rhs)->fFractionDifference < (*lhs)->fFractionDifference) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000118 return -1;
119 }
120 return 0;
121}
122
123static int compare_diff_weighted (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000124 if ((*lhs)->fWeightedFraction < (*rhs)->fWeightedFraction) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000125 return 1;
126 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000127 if ((*lhs)->fWeightedFraction > (*rhs)->fWeightedFraction) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000128 return -1;
129 }
130 return 0;
131}
132
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000133/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
134/// from largest to smallest.
135static int compare_diff_mean_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
136 float leftValue = MAX3((*lhs)->fAverageMismatchR,
137 (*lhs)->fAverageMismatchG,
138 (*lhs)->fAverageMismatchB);
139 float rightValue = MAX3((*rhs)->fAverageMismatchR,
140 (*rhs)->fAverageMismatchG,
141 (*rhs)->fAverageMismatchB);
142 if (leftValue < rightValue) {
143 return 1;
144 }
145 if (rightValue < leftValue) {
146 return -1;
147 }
148 return 0;
149}
150
151/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB})
152/// from largest to smallest.
153static int compare_diff_max_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
154 float leftValue = MAX3((*lhs)->fMaxMismatchR,
155 (*lhs)->fMaxMismatchG,
156 (*lhs)->fMaxMismatchB);
157 float rightValue = MAX3((*rhs)->fMaxMismatchR,
158 (*rhs)->fMaxMismatchG,
159 (*rhs)->fMaxMismatchB);
160 if (leftValue < rightValue) {
161 return 1;
162 }
163 if (rightValue < leftValue) {
164 return -1;
165 }
166 return compare_diff_mean_mismatches(lhs, rhs);
167}
168
169
170
171/// Parameterized routine to compute the color of a pixel in a difference image.
172typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
173
tomhudson@google.com4e305982011-07-13 17:42:46 +0000174static bool get_bitmaps (DiffRecord* diffRecord) {
175 SkFILEStream compareStream(diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000176 if (!compareStream.isValid()) {
177 SkDebugf("WARNING: couldn't open comparison file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000178 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000179 return false;
180 }
181
tomhudson@google.com4e305982011-07-13 17:42:46 +0000182 SkFILEStream baseStream(diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000183 if (!baseStream.isValid()) {
184 SkDebugf("ERROR: couldn't open base file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000185 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000186 return false;
187 }
188
189 SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
190 if (NULL == codec) {
191 SkDebugf("ERROR: no codec found for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000192 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000193 return false;
194 }
195
196 SkAutoTDelete<SkImageDecoder> ad(codec);
197
198 baseStream.rewind();
199 if (!codec->decode(&baseStream, &diffRecord->fBaseBitmap,
200 SkBitmap::kARGB_8888_Config,
201 SkImageDecoder::kDecodePixels_Mode)) {
202 SkDebugf("ERROR: codec failed for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000203 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000204 return false;
205 }
206
207 if (!codec->decode(&compareStream, &diffRecord->fComparisonBitmap,
208 SkBitmap::kARGB_8888_Config,
209 SkImageDecoder::kDecodePixels_Mode)) {
210 SkDebugf("ERROR: codec failed for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000211 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000212 return false;
213 }
214
215 return true;
216}
217
218// from gm - thanks to PNG, we need to force all pixels 100% opaque
219static void force_all_opaque(const SkBitmap& bitmap) {
220 SkAutoLockPixels lock(bitmap);
221 for (int y = 0; y < bitmap.height(); y++) {
222 for (int x = 0; x < bitmap.width(); x++) {
223 *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
224 }
225 }
226}
227
228// from gm
229static bool write_bitmap(const SkString& path, const SkBitmap& bitmap) {
230 SkBitmap copy;
231 bitmap.copyTo(&copy, SkBitmap::kARGB_8888_Config);
232 force_all_opaque(copy);
233 return SkImageEncoder::EncodeFile(path.c_str(), copy,
234 SkImageEncoder::kPNG_Type, 100);
235}
236
237// from gm
238static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
239 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
240 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
241 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
242
243 return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
244}
245
246/// Returns white on every pixel so that differences jump out at you;
247/// makes it easy to spot areas of difference that are in the least-significant
248/// bits.
249static inline SkPMColor compute_diff_white(SkPMColor c0, SkPMColor c1) {
250 return SkPackARGB32(0xFF, 0xFF, 0xFF, 0xFF);
251}
252
253static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1,
254 const int threshold) {
255 int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
256 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
257 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
258 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
259
260 return ((SkAbs32(da) <= threshold) &&
261 (SkAbs32(dr) <= threshold) &&
262 (SkAbs32(dg) <= threshold) &&
263 (SkAbs32(db) <= threshold));
264}
265
266// based on gm
267static void compute_diff(DiffRecord* dr,
268 DiffMetricProc diffFunction,
269 const int colorThreshold) {
270 SkAutoLockPixels alp(dr->fDifferenceBitmap);
271
272 const int w = dr->fComparisonBitmap.width();
273 const int h = dr->fComparisonBitmap.height();
274 int mismatchedPixels = 0;
275 int totalMismatchR = 0;
276 int totalMismatchG = 0;
277 int totalMismatchB = 0;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000278 // Accumulate fractionally different pixels, then divide out
279 // # of pixels at the end.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000280 dr->fWeightedFraction = 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000281 for (int y = 0; y < h; y++) {
282 for (int x = 0; x < w; x++) {
283 SkPMColor c0 = *dr->fBaseBitmap.getAddr32(x, y);
284 SkPMColor c1 = *dr->fComparisonBitmap.getAddr32(x, y);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000285 SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
286 SkPMColor outputDifference = diffFunction(c0, c1);
287 uint32_t thisR = SkGetPackedR32(trueDifference);
288 uint32_t thisG = SkGetPackedG32(trueDifference);
289 uint32_t thisB = SkGetPackedB32(trueDifference);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000290 totalMismatchR += thisR;
291 totalMismatchG += thisG;
292 totalMismatchB += thisB;
293 // In HSV, value is defined as max RGB component.
294 int value = MAX3(thisR, thisG, thisB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000295 dr->fWeightedFraction += ((float) value) / 255;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000296 if (thisR > dr->fMaxMismatchR) {
297 dr->fMaxMismatchR = thisR;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000298 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000299 if (thisG > dr->fMaxMismatchG) {
300 dr->fMaxMismatchG = thisG;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000301 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000302 if (thisB > dr->fMaxMismatchB) {
303 dr->fMaxMismatchB = thisB;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000304 }
305 if (!colors_match_thresholded(c0, c1, colorThreshold)) {
306 mismatchedPixels++;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000307 *dr->fDifferenceBitmap.getAddr32(x, y) = outputDifference;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000308 } else {
309 *dr->fDifferenceBitmap.getAddr32(x, y) = 0;
310 }
311 }
312 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000313 int pixelCount = w * h;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000314 dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
315 dr->fWeightedFraction /= pixelCount;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000316 dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
317 dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
318 dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000319}
320
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000321/// Given a image filename, returns the name of the file containing the
322/// associated difference image.
323static SkString filename_to_diff_filename (const SkString& filename) {
324 SkString diffName (filename);
325 const char* cstring = diffName.c_str();
326 int dotOffset = strrchr(cstring, '.') - cstring;
327 diffName.remove(dotOffset, diffName.size() - dotOffset);
328 diffName.append("-diff.png");
329 return diffName;
330}
331
tomhudson@google.com4e305982011-07-13 17:42:46 +0000332static void create_diff_image(DiffRecord* drp,
333 DiffMetricProc dmp,
334 const int colorThreshold) {
335 const int w = drp->fBaseBitmap.width();
336 const int h = drp->fBaseBitmap.height();
337 drp->fDifferenceBitmap.setConfig(SkBitmap::kARGB_8888_Config, w, h);
338 drp->fDifferenceBitmap.allocPixels();
339 compute_diff(drp, dmp, colorThreshold);
340}
341
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000342/// Creates difference images, returns the number that have a 0 metric.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000343static void create_diff_images (DiffMetricProc dmp,
344 SkQSortCompareProc scp,
345 const int colorThreshold,
346 RecordArray* differences,
347 const SkString& baseDir,
348 const SkString& comparisonDir,
349 const SkString& outputDir,
350 DiffSummary* summary) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000351
352 //@todo thudson 19 Apr 2011
353 // this lets us know about files in baseDir not in compareDir, but it
354 // doesn't detect files in compareDir not in baseDir. Doing that
355 // efficiently seems to imply iterating through both directories to
356 // create a merged list, and then attempting to process every entry
357 // in that list?
358
359 SkOSFile::Iter baseIterator (baseDir.c_str());
360 SkString filename;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000361 while (baseIterator.next(&filename)) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000362 if (filename.endsWith(".pdf")) {
363 continue;
364 }
tomhudson@google.com4e305982011-07-13 17:42:46 +0000365 SkString basePath (baseDir);
366 SkString comparisonPath (comparisonDir);
367 basePath.append(filename);
368 comparisonPath.append(filename);
369 DiffRecord * drp = new DiffRecord (filename, basePath, comparisonPath);
370 if (!get_bitmaps(drp)) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000371 continue;
372 }
373
tomhudson@google.com4e305982011-07-13 17:42:46 +0000374 create_diff_image(drp, dmp, colorThreshold);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000375
376 SkString outPath (outputDir);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000377 outPath.append(filename_to_diff_filename(filename));
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000378 write_bitmap(outPath, drp->fDifferenceBitmap);
379
380 differences->push(drp);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000381 summary->add(drp);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000382 }
383
384 SkQSort(differences->begin(), differences->count(), sizeof(DiffRecord*),
385 scp);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000386}
387
388/// Make layout more consistent by scaling image to 240 height, 360 width,
389/// or natural size, whichever is smallest.
390static int compute_image_height (const SkBitmap& bmp) {
391 int retval = 240;
392 if (bmp.height() < retval) {
393 retval = bmp.height();
394 }
395 float scale = (float) retval / bmp.height();
396 if (bmp.width() * scale > 360) {
397 scale = (float) 360 / bmp.width();
398 retval = bmp.height() * scale;
399 }
400 return retval;
401}
402
403static void print_page_header (SkFILEWStream* stream,
404 const int matchCount,
405 const int colorThreshold,
406 const RecordArray& differences) {
407 SkTime::DateTime dt;
408 SkTime::GetDateTime(&dt);
409 stream->writeText("SkDiff run at ");
410 stream->writeDecAsText(dt.fHour);
411 stream->writeText(":");
412 if (dt.fMinute < 10) {
413 stream->writeText("0");
414 }
415 stream->writeDecAsText(dt.fMinute);
416 stream->writeText(":");
417 if (dt.fSecond < 10) {
418 stream->writeText("0");
419 }
420 stream->writeDecAsText(dt.fSecond);
421 stream->writeText("<br>");
422 stream->writeDecAsText(matchCount);
423 stream->writeText(" of ");
424 stream->writeDecAsText(differences.count());
425 stream->writeText(" images matched ");
426 if (colorThreshold == 0) {
427 stream->writeText("exactly");
428 } else {
429 stream->writeText("within ");
430 stream->writeDecAsText(colorThreshold);
431 stream->writeText(" color units per component");
432 }
433 stream->writeText(".<br>");
434
435}
436
437static void print_pixel_count (SkFILEWStream* stream,
438 const DiffRecord& diff) {
439 stream->writeText("<br>(");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000440 stream->writeDecAsText(diff.fFractionDifference *
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000441 diff.fBaseBitmap.width() *
442 diff.fBaseBitmap.height());
443 stream->writeText(" pixels)");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000444/*
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000445 stream->writeDecAsText(diff.fWeightedFraction *
tomhudson@google.com5b325292011-05-24 19:41:13 +0000446 diff.fBaseBitmap.width() *
447 diff.fBaseBitmap.height());
448 stream->writeText(" weighted pixels)");
449*/
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000450}
451
452static void print_label_cell (SkFILEWStream* stream,
453 const DiffRecord& diff) {
454 stream->writeText("<td>");
455 stream->writeText(diff.fFilename.c_str());
456 stream->writeText("<br>");
457 char metricBuf [20];
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000458 sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000459 stream->writeText(metricBuf);
460 stream->writeText(" of pixels differ");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000461 stream->writeText("\n (");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000462 sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000463 stream->writeText(metricBuf);
464 stream->writeText(" weighted)");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000465 // Write the actual number of pixels that differ if it's < 1%
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000466 if (diff.fFractionDifference < 0.01) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000467 print_pixel_count(stream, diff);
468 }
469 stream->writeText("<br>Average color mismatch ");
470 stream->writeDecAsText(MAX3(diff.fAverageMismatchR,
471 diff.fAverageMismatchG,
472 diff.fAverageMismatchB));
473 stream->writeText("<br>Max color mismatch ");
474 stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
475 diff.fMaxMismatchG,
476 diff.fMaxMismatchB));
477 stream->writeText("</td>");
478}
479
480static void print_image_cell (SkFILEWStream* stream,
tomhudson@google.com4e305982011-07-13 17:42:46 +0000481 const SkString& path,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000482 int height) {
483 stream->writeText("<td><a href=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000484 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000485 stream->writeText("\"><img src=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000486 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000487 stream->writeText("\" height=\"");
488 stream->writeDecAsText(height);
489 stream->writeText("px\"></a></td>");
490}
491
492static void print_diff_page (const int matchCount,
493 const int colorThreshold,
494 const RecordArray& differences,
495 const SkString& baseDir,
496 const SkString& comparisonDir,
497 const SkString& outputDir) {
498
tomhudson@google.com5b325292011-05-24 19:41:13 +0000499 SkString outputPath (outputDir);
500 outputPath.append("index.html");
501 //SkFILEWStream outputStream ("index.html");
502 SkFILEWStream outputStream (outputPath.c_str());
503
tomhudson@google.com4e305982011-07-13 17:42:46 +0000504 // Need to convert paths from relative-to-cwd to relative-to-outputDir
tomhudson@google.com5b325292011-05-24 19:41:13 +0000505 // FIXME this doesn't work if there are '..' inside the outputDir
506 unsigned int ui;
507 SkString relativePath;
508 for (ui = 0; ui < outputDir.size(); ui++) {
509 if (outputDir[ui] == '/') {
510 relativePath.append("../");
511 }
512 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000513
514 outputStream.writeText("<html>\n<body>\n");
515 print_page_header(&outputStream, matchCount, colorThreshold, differences);
516
517 outputStream.writeText("<table>\n");
518 int i;
519 for (i = 0; i < differences.count(); i++) {
520 DiffRecord* diff = differences[i];
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000521 if (0 == diff->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000522 continue;
523 }
tomhudson@google.com4e305982011-07-13 17:42:46 +0000524 if (!diff->fBasePath.startsWith("/")) {
525 diff->fBasePath.prepend(relativePath);
526 }
527 if (!diff->fComparisonPath.startsWith("/")) {
528 diff->fComparisonPath.prepend(relativePath);
529 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000530 int height = compute_image_height(diff->fBaseBitmap);
531 outputStream.writeText("<tr>\n");
532 print_label_cell(&outputStream, *diff);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000533 print_image_cell(&outputStream, diff->fBasePath, height);
534 print_image_cell(&outputStream,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000535 filename_to_diff_filename(diff->fFilename), height);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000536 print_image_cell(&outputStream, diff->fComparisonPath, height);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000537 outputStream.writeText("</tr>\n");
538 outputStream.flush();
539 }
540 outputStream.writeText("</table>\n");
541 outputStream.writeText("</body>\n</html>\n");
542 outputStream.flush();
543}
544
545static void usage (char * argv0) {
546 SkDebugf("Skia baseline image diff tool\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000547 SkDebugf("Usage: %s baseDir comparisonDir [outputDir]\n", argv0);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000548 SkDebugf(
549" -white: force all difference pixels to white\n"
550" -threshold n: only report differences > n (in one channel) [default 0]\n"
551" -sortbymismatch: sort by average color channel mismatch\n");
552 SkDebugf(
553" -sortbymaxmismatch: sort by worst color channel mismatch,\n"
554" break ties with -sortbymismatch,\n"
555" [default by fraction of pixels mismatching]\n");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000556 SkDebugf(
557" -weighted: sort by # pixels different weighted by color difference\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000558 SkDebugf(" baseDir: directory to read baseline images from\n");
559 SkDebugf(" comparisonDir: directory to read comparison images from\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000560 SkDebugf(
561" outputDir: directory to write difference images to; defaults to\n"
562" comparisonDir\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000563 SkDebugf("Also creates an \"index.html\" file in the current directory.\n");
564}
565
566int main (int argc, char ** argv) {
567 DiffMetricProc diffProc = compute_diff_pmcolor;
568 SkQSortCompareProc sortProc = (SkQSortCompareProc) compare_diff_metrics;
569
570 // Maximum error tolerated in any one color channel in any one pixel before
571 // a difference is reported.
572 int colorThreshold = 0;
573 SkString baseDir;
574 SkString comparisonDir;
575 SkString outputDir;
576
577 RecordArray differences;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000578 DiffSummary summary;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000579
580 int i, j;
581 for (i = 1, j = 0; i < argc; i++) {
582 if (!strcmp(argv[i], "-help")) {
583 usage(argv[0]);
584 return 0;
585 }
586 if (!strcmp(argv[i], "-white")) {
587 diffProc = compute_diff_white;
588 continue;
589 }
590 if (!strcmp(argv[i], "-sortbymismatch")) {
591 sortProc = (SkQSortCompareProc) compare_diff_mean_mismatches;
592 continue;
593 }
594 if (!strcmp(argv[i], "-sortbymaxmismatch")) {
595 sortProc = (SkQSortCompareProc) compare_diff_max_mismatches;
596 continue;
597 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000598 if (!strcmp(argv[i], "-weighted")) {
599 sortProc = (SkQSortCompareProc) compare_diff_weighted;
600 continue;
601 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000602 if (!strcmp(argv[i], "-threshold")) {
603 colorThreshold = atoi(argv[++i]);
604 continue;
605 }
606 if (argv[i][0] != '-') {
607 switch (j++) {
608 case 0:
609 baseDir.set(argv[i]);
610 continue;
611 case 1:
612 comparisonDir.set(argv[i]);
613 continue;
614 case 2:
615 outputDir.set(argv[i]);
616 continue;
617 default:
618 usage(argv[0]);
619 return 0;
620 }
621 }
622
623 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
624 usage(argv[0]);
625 return 0;
626 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000627 if (j == 2) {
628 outputDir = comparisonDir;
629 } else if (j != 3) {
630 usage(argv[0]);
631 return 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000632 }
633
634 if (!baseDir.endsWith("/")) {
635 baseDir.append("/");
636 }
637 if (!comparisonDir.endsWith("/")) {
638 comparisonDir.append("/");
639 }
640 if (!outputDir.endsWith("/")) {
641 outputDir.append("/");
642 }
643
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000644 create_diff_images(diffProc, sortProc, colorThreshold, &differences,
645 baseDir, comparisonDir, outputDir, &summary);
646 summary.print();
647 print_diff_page(summary.fNumMatches, colorThreshold, differences,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000648 baseDir, comparisonDir, outputDir);
649
650 for (i = 0; i < differences.count(); i++) {
651 delete differences[i];
652 }
653}