blob: a542dc8077dd18dc3732f94490e91eebd46e626d [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
bsalomon@google.com1a315fe2011-09-23 14:56:37 +000035#if SK_BUILD_FOR_WIN32
36 #define PATH_DIV_STR "\\"
37 #define PATH_DIV_CHAR '\\'
38#else
39 #define PATH_DIV_STR "/"
40 #define PATH_DIV_CHAR '/'
41#endif
42
tomhudson@google.com4b33d282011-04-27 15:39:30 +000043struct DiffRecord {
tomhudson@google.com4e305982011-07-13 17:42:46 +000044 DiffRecord (const SkString filename,
45 const SkString basePath,
epoger@google.com5fd53852012-03-22 18:20:06 +000046 const SkString comparisonPath,
47 bool baseMissing = false,
48 bool comparisonMissing = false)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000049 : fFilename (filename)
tomhudson@google.com4e305982011-07-13 17:42:46 +000050 , fBasePath (basePath)
51 , fComparisonPath (comparisonPath)
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000052 , fBaseBitmap (new SkBitmap ())
53 , fComparisonBitmap (new SkBitmap ())
54 , fDifferenceBitmap (new SkBitmap ())
epoger@google.com25d961c2012-02-02 20:50:36 +000055 , fWhiteBitmap (new SkBitmap ())
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000056 , fBaseHeight (0)
57 , fBaseWidth (0)
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000058 , fFractionDifference (0)
59 , fWeightedFraction (0)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000060 , fAverageMismatchR (0)
61 , fAverageMismatchG (0)
62 , fAverageMismatchB (0)
63 , fMaxMismatchR (0)
64 , fMaxMismatchG (0)
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +000065 , fMaxMismatchB (0)
epoger@google.com5fd53852012-03-22 18:20:06 +000066 , fDoImageSizesMismatch (false)
67 , fBaseMissing(baseMissing)
68 , fComparisonMissing(comparisonMissing) {
tomhudson@google.com7d042802011-07-14 13:15:55 +000069 // These asserts are valid for GM, but not for --chromium
70 //SkASSERT(basePath.endsWith(filename.c_str()));
71 //SkASSERT(comparisonPath.endsWith(filename.c_str()));
tomhudson@google.com4e305982011-07-13 17:42:46 +000072 };
tomhudson@google.com4b33d282011-04-27 15:39:30 +000073
74 SkString fFilename;
tomhudson@google.com4e305982011-07-13 17:42:46 +000075 SkString fBasePath;
76 SkString fComparisonPath;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000077
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000078 SkBitmap* fBaseBitmap;
79 SkBitmap* fComparisonBitmap;
80 SkBitmap* fDifferenceBitmap;
epoger@google.com25d961c2012-02-02 20:50:36 +000081 SkBitmap* fWhiteBitmap;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000082
83 int fBaseHeight;
84 int fBaseWidth;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000085
86 /// Arbitrary floating-point metric to be used to sort images from most
87 /// to least different from baseline; values of 0 will be omitted from the
88 /// summary webpage.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000089 float fFractionDifference;
90 float fWeightedFraction;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000091
92 float fAverageMismatchR;
93 float fAverageMismatchG;
94 float fAverageMismatchB;
95
96 uint32_t fMaxMismatchR;
97 uint32_t fMaxMismatchG;
98 uint32_t fMaxMismatchB;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +000099
100 /// By the time we need to report image size mismatch, we've already
101 /// released the bitmaps, so we need to remember it when we detect it.
102 bool fDoImageSizesMismatch;
epoger@google.com5fd53852012-03-22 18:20:06 +0000103
104 bool fBaseMissing;
105 bool fComparisonMissing;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000106};
107
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000108#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
109#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
110
epoger@google.com25d961c2012-02-02 20:50:36 +0000111const SkPMColor PMCOLOR_WHITE = SkPreMultiplyColor(SK_ColorWHITE);
112const SkPMColor PMCOLOR_BLACK = SkPreMultiplyColor(SK_ColorBLACK);
113
epoger@google.com5fd53852012-03-22 18:20:06 +0000114typedef SkTDArray<SkString*> FileArray;
115
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000116struct DiffSummary {
117 DiffSummary ()
118 : fNumMatches (0)
119 , fNumMismatches (0)
120 , fMaxMismatchV (0)
121 , fMaxMismatchPercent (0) { };
122
epoger@google.com5fd53852012-03-22 18:20:06 +0000123 ~DiffSummary() {
124 fBaseMissing.deleteAll();
125 fComparisonMissing.deleteAll();
126 }
127
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000128 uint32_t fNumMatches;
129 uint32_t fNumMismatches;
130 uint32_t fMaxMismatchV;
131 float fMaxMismatchPercent;
132
epoger@google.com5fd53852012-03-22 18:20:06 +0000133 FileArray fBaseMissing;
134 FileArray fComparisonMissing;
135
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000136 void print () {
epoger@google.com5fd53852012-03-22 18:20:06 +0000137 int n = fBaseMissing.count();
138 if (n > 0) {
139 printf("Missing in baseDir:\n");
140 for (int i = 0; i < n; ++i) {
141 printf("\t%s\n", fBaseMissing[i]->c_str());
142 }
143 }
144 n = fComparisonMissing.count();
145 if (n > 0) {
146 printf("Missing in comparisonDir:\n");
147 for (int i = 0; i < n; ++i) {
148 printf("\t%s\n", fComparisonMissing[i]->c_str());
149 }
150 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000151 printf("%d of %d images matched.\n", fNumMatches,
152 fNumMatches + fNumMismatches);
153 if (fNumMismatches > 0) {
154 printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
155 printf("Largest area mismatch was %.2f%% of pixels\n",
156 fMaxMismatchPercent);
157 }
158
159 }
160
161 void add (DiffRecord* drp) {
epoger@google.com5fd53852012-03-22 18:20:06 +0000162 if (drp->fBaseMissing) {
163 fBaseMissing.push(new SkString(drp->fFilename));
164 fNumMismatches++;
165 } else if (drp->fComparisonMissing) {
166 fComparisonMissing.push(new SkString(drp->fFilename));
167 fNumMismatches++;
168 } else if (0 == drp->fFractionDifference) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000169 fNumMatches++;
170 } else {
171 fNumMismatches++;
172 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
173 fMaxMismatchPercent = drp->fFractionDifference * 100;
174 }
tomhudson@google.com88a0e052011-06-09 18:54:01 +0000175 uint32_t value = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
176 drp->fMaxMismatchB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000177 if (value > fMaxMismatchV) {
178 fMaxMismatchV = value;
179 }
180 }
181 }
182};
183
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000184typedef SkTDArray<DiffRecord*> RecordArray;
185
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000186/// Comparison routine for qsort; sorts by fFractionDifference
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000187/// from largest to smallest.
188static int compare_diff_metrics (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000189 if ((*lhs)->fFractionDifference < (*rhs)->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000190 return 1;
191 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000192 if ((*rhs)->fFractionDifference < (*lhs)->fFractionDifference) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000193 return -1;
194 }
195 return 0;
196}
197
198static int compare_diff_weighted (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000199 if ((*lhs)->fWeightedFraction < (*rhs)->fWeightedFraction) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000200 return 1;
201 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000202 if ((*lhs)->fWeightedFraction > (*rhs)->fWeightedFraction) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000203 return -1;
204 }
205 return 0;
206}
207
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000208/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
209/// from largest to smallest.
210static int compare_diff_mean_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
211 float leftValue = MAX3((*lhs)->fAverageMismatchR,
212 (*lhs)->fAverageMismatchG,
213 (*lhs)->fAverageMismatchB);
214 float rightValue = MAX3((*rhs)->fAverageMismatchR,
215 (*rhs)->fAverageMismatchG,
216 (*rhs)->fAverageMismatchB);
217 if (leftValue < rightValue) {
218 return 1;
219 }
220 if (rightValue < leftValue) {
221 return -1;
222 }
223 return 0;
224}
225
226/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB})
227/// from largest to smallest.
228static int compare_diff_max_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000229 uint32_t leftValue = MAX3((*lhs)->fMaxMismatchR,
230 (*lhs)->fMaxMismatchG,
231 (*lhs)->fMaxMismatchB);
232 uint32_t rightValue = MAX3((*rhs)->fMaxMismatchR,
233 (*rhs)->fMaxMismatchG,
234 (*rhs)->fMaxMismatchB);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000235 if (leftValue < rightValue) {
236 return 1;
237 }
238 if (rightValue < leftValue) {
239 return -1;
240 }
241 return compare_diff_mean_mismatches(lhs, rhs);
242}
243
244
245
246/// Parameterized routine to compute the color of a pixel in a difference image.
247typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
248
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000249static void expand_and_copy (int width, int height, SkBitmap** dest) {
250 SkBitmap* temp = new SkBitmap ();
251 temp->reset();
252 temp->setConfig((*dest)->config(), width, height);
253 temp->allocPixels();
254 (*dest)->copyPixelsTo(temp->getPixels(), temp->getSize(),
255 temp->rowBytes());
256 *dest = temp;
257}
258
tomhudson@google.com4e305982011-07-13 17:42:46 +0000259static bool get_bitmaps (DiffRecord* diffRecord) {
260 SkFILEStream compareStream(diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000261 if (!compareStream.isValid()) {
262 SkDebugf("WARNING: couldn't open comparison file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000263 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000264 return false;
265 }
266
tomhudson@google.com4e305982011-07-13 17:42:46 +0000267 SkFILEStream baseStream(diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000268 if (!baseStream.isValid()) {
269 SkDebugf("ERROR: couldn't open base file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000270 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000271 return false;
272 }
273
274 SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
275 if (NULL == codec) {
276 SkDebugf("ERROR: no codec found for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000277 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000278 return false;
279 }
280
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000281 // In debug, the DLL will automatically be unloaded when this is deleted,
282 // but that shouldn't be a problem in release mode.
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000283 SkAutoTDelete<SkImageDecoder> ad(codec);
284
285 baseStream.rewind();
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000286 if (!codec->decode(&baseStream, diffRecord->fBaseBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000287 SkBitmap::kARGB_8888_Config,
288 SkImageDecoder::kDecodePixels_Mode)) {
289 SkDebugf("ERROR: codec failed for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000290 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000291 return false;
292 }
293
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000294 diffRecord->fBaseWidth = diffRecord->fBaseBitmap->width();
295 diffRecord->fBaseHeight = diffRecord->fBaseBitmap->height();
296
297 if (!codec->decode(&compareStream, diffRecord->fComparisonBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000298 SkBitmap::kARGB_8888_Config,
299 SkImageDecoder::kDecodePixels_Mode)) {
300 SkDebugf("ERROR: codec failed for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000301 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000302 return false;
303 }
304
305 return true;
306}
307
epoger@google.com5fd53852012-03-22 18:20:06 +0000308static bool get_bitmap_height_width(const SkString& path,
309 int *height, int *width) {
310 SkFILEStream stream(path.c_str());
311 if (!stream.isValid()) {
312 SkDebugf("ERROR: couldn't open file <%s>\n",
313 path.c_str());
314 return false;
315 }
316
317 SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
318 if (NULL == codec) {
319 SkDebugf("ERROR: no codec found for <%s>\n",
320 path.c_str());
321 return false;
322 }
323
324 SkAutoTDelete<SkImageDecoder> ad(codec);
325 SkBitmap bm;
326
327 stream.rewind();
328 if (!codec->decode(&stream, &bm,
329 SkBitmap::kARGB_8888_Config,
330 SkImageDecoder::kDecodePixels_Mode)) {
331 SkDebugf("ERROR: codec failed for <%s>\n",
332 path.c_str());
333 return false;
334 }
335
336 *height = bm.height();
337 *width = bm.width();
338
339 return true;
340}
341
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000342// from gm - thanks to PNG, we need to force all pixels 100% opaque
343static void force_all_opaque(const SkBitmap& bitmap) {
344 SkAutoLockPixels lock(bitmap);
345 for (int y = 0; y < bitmap.height(); y++) {
346 for (int x = 0; x < bitmap.width(); x++) {
347 *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
348 }
349 }
350}
351
352// from gm
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000353static bool write_bitmap(const SkString& path, const SkBitmap* bitmap) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000354 SkBitmap copy;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000355 bitmap->copyTo(&copy, SkBitmap::kARGB_8888_Config);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000356 force_all_opaque(copy);
357 return SkImageEncoder::EncodeFile(path.c_str(), copy,
358 SkImageEncoder::kPNG_Type, 100);
359}
360
361// from gm
362static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
363 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
364 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
365 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
366
367 return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
368}
369
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000370static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1,
371 const int threshold) {
372 int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
373 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
374 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
375 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
376
377 return ((SkAbs32(da) <= threshold) &&
378 (SkAbs32(dr) <= threshold) &&
379 (SkAbs32(dg) <= threshold) &&
380 (SkAbs32(db) <= threshold));
381}
382
383// based on gm
384static void compute_diff(DiffRecord* dr,
385 DiffMetricProc diffFunction,
386 const int colorThreshold) {
epoger@google.com25d961c2012-02-02 20:50:36 +0000387 SkAutoLockPixels alpDiff(*dr->fDifferenceBitmap);
388 SkAutoLockPixels alpWhite(*dr->fWhiteBitmap);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000389
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000390 const int w = dr->fComparisonBitmap->width();
391 const int h = dr->fComparisonBitmap->height();
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000392 int mismatchedPixels = 0;
393 int totalMismatchR = 0;
394 int totalMismatchG = 0;
395 int totalMismatchB = 0;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000396
397 if (w != dr->fBaseWidth || h != dr->fBaseHeight) {
398 dr->fDoImageSizesMismatch = true;
399 dr->fFractionDifference = 1;
400 return;
401 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000402 // Accumulate fractionally different pixels, then divide out
403 // # of pixels at the end.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000404 dr->fWeightedFraction = 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000405 for (int y = 0; y < h; y++) {
406 for (int x = 0; x < w; x++) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000407 SkPMColor c0 = *dr->fBaseBitmap->getAddr32(x, y);
408 SkPMColor c1 = *dr->fComparisonBitmap->getAddr32(x, y);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000409 SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
410 SkPMColor outputDifference = diffFunction(c0, c1);
411 uint32_t thisR = SkGetPackedR32(trueDifference);
412 uint32_t thisG = SkGetPackedG32(trueDifference);
413 uint32_t thisB = SkGetPackedB32(trueDifference);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000414 totalMismatchR += thisR;
415 totalMismatchG += thisG;
416 totalMismatchB += thisB;
417 // In HSV, value is defined as max RGB component.
418 int value = MAX3(thisR, thisG, thisB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000419 dr->fWeightedFraction += ((float) value) / 255;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000420 if (thisR > dr->fMaxMismatchR) {
421 dr->fMaxMismatchR = thisR;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000422 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000423 if (thisG > dr->fMaxMismatchG) {
424 dr->fMaxMismatchG = thisG;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000425 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000426 if (thisB > dr->fMaxMismatchB) {
427 dr->fMaxMismatchB = thisB;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000428 }
429 if (!colors_match_thresholded(c0, c1, colorThreshold)) {
430 mismatchedPixels++;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000431 *dr->fDifferenceBitmap->getAddr32(x, y) = outputDifference;
epoger@google.com25d961c2012-02-02 20:50:36 +0000432 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_WHITE;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000433 } else {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000434 *dr->fDifferenceBitmap->getAddr32(x, y) = 0;
epoger@google.com25d961c2012-02-02 20:50:36 +0000435 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_BLACK;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000436 }
437 }
438 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000439 int pixelCount = w * h;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000440 dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
441 dr->fWeightedFraction /= pixelCount;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000442 dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
443 dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
444 dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000445}
446
epoger@google.com25d961c2012-02-02 20:50:36 +0000447static SkString filename_to_derived_filename (const SkString& filename,
448 const char *suffix) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000449 SkString diffName (filename);
450 const char* cstring = diffName.c_str();
451 int dotOffset = strrchr(cstring, '.') - cstring;
452 diffName.remove(dotOffset, diffName.size() - dotOffset);
epoger@google.com25d961c2012-02-02 20:50:36 +0000453 diffName.append(suffix);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000454 return diffName;
455}
456
epoger@google.com25d961c2012-02-02 20:50:36 +0000457/// Given a image filename, returns the name of the file containing the
458/// associated difference image.
459static SkString filename_to_diff_filename (const SkString& filename) {
460 return filename_to_derived_filename(filename, "-diff.png");
461}
462
463/// Given a image filename, returns the name of the file containing the
464/// "white" difference image.
465static SkString filename_to_white_filename (const SkString& filename) {
466 return filename_to_derived_filename(filename, "-white.png");
467}
468
tomhudson@google.com7d042802011-07-14 13:15:55 +0000469/// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo-actual.png"
470static SkString chrome_expected_path_to_actual (const SkString& expected) {
471 SkString actualPath (expected);
472 actualPath.remove(actualPath.size() - 13, 13);
473 actualPath.append("-actual.png");
474 return actualPath;
475}
476
477/// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo.png"
478static SkString chrome_expected_name_to_short (const SkString& expected) {
479 SkString shortName (expected);
480 shortName.remove(shortName.size() - 13, 13);
481 shortName.append(".png");
482 return shortName;
483}
484
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000485static void release_bitmaps(DiffRecord* drp) {
486 delete drp->fBaseBitmap;
487 drp->fBaseBitmap = NULL;
488 delete drp->fComparisonBitmap;
489 drp->fComparisonBitmap = NULL;
490 delete drp->fDifferenceBitmap;
491 drp->fDifferenceBitmap = NULL;
epoger@google.com25d961c2012-02-02 20:50:36 +0000492 delete drp->fWhiteBitmap;
493 drp->fWhiteBitmap = NULL;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000494}
495
tomhudson@google.com7d042802011-07-14 13:15:55 +0000496
497static void create_and_write_diff_image(DiffRecord* drp,
498 DiffMetricProc dmp,
499 const int colorThreshold,
500 const SkString& outputDir,
501 const SkString& filename) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000502 const int w = drp->fBaseWidth;
503 const int h = drp->fBaseHeight;
504 drp->fDifferenceBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
505 drp->fDifferenceBitmap->allocPixels();
epoger@google.com25d961c2012-02-02 20:50:36 +0000506 drp->fWhiteBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
507 drp->fWhiteBitmap->allocPixels();
tomhudson@google.com4e305982011-07-13 17:42:46 +0000508 compute_diff(drp, dmp, colorThreshold);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000509
epoger@google.com25d961c2012-02-02 20:50:36 +0000510 SkString differencePath (outputDir);
511 differencePath.append(filename_to_diff_filename(filename));
512 write_bitmap(differencePath, drp->fDifferenceBitmap);
513 SkString whitePath (outputDir);
514 whitePath.append(filename_to_white_filename(filename));
515 write_bitmap(whitePath, drp->fWhiteBitmap);
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000516 release_bitmaps(drp);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000517}
518
epoger@google.com5fd53852012-03-22 18:20:06 +0000519/// Iterate dir and get all files except 'pdf' files.
520static void get_file_list(const SkString& dir, FileArray *files) {
521 SkOSFile::Iter it(dir.c_str());
522 SkString filename;
523 while (it.next(&filename)) {
524 if (filename.endsWith(".pdf")) {
525 continue;
526 }
527
528 files->push(new SkString(filename));
529 }
530}
531
532static void release_file_list(FileArray *files) {
533 files->deleteAll();
534}
535
536/// Comparison routines for qsort, sort by file names.
537static int compare_file_name_metrics(SkString **lhs, SkString **rhs) {
538 return strcmp((*lhs)->c_str(), (*rhs)->c_str());
539}
540
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000541/// Creates difference images, returns the number that have a 0 metric.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000542static void create_diff_images (DiffMetricProc dmp,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000543 const int colorThreshold,
544 RecordArray* differences,
545 const SkString& baseDir,
546 const SkString& comparisonDir,
547 const SkString& outputDir,
548 DiffSummary* summary) {
epoger@google.com5fd53852012-03-22 18:20:06 +0000549 SkQSortCompareProc sortFileProc =
550 (SkQSortCompareProc)compare_file_name_metrics;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000551
epoger@google.com5fd53852012-03-22 18:20:06 +0000552 FileArray baseFiles;
553 FileArray comparisonFiles;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000554
epoger@google.com5fd53852012-03-22 18:20:06 +0000555 get_file_list(baseDir, &baseFiles);
556 get_file_list(comparisonDir, &comparisonFiles);
557
558 SkQSort(baseFiles.begin(), baseFiles.count(),
559 sizeof(SkString*), sortFileProc);
560 SkQSort(comparisonFiles.begin(), comparisonFiles.count(),
561 sizeof(SkString*), sortFileProc);
562
563 int i = 0;
564 int j = 0;
565
566 while (i < baseFiles.count() &&
567 j < comparisonFiles.count()) {
568
tomhudson@google.com4e305982011-07-13 17:42:46 +0000569 SkString basePath (baseDir);
epoger@google.com5fd53852012-03-22 18:20:06 +0000570 basePath.append(*baseFiles[i]);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000571 SkString comparisonPath (comparisonDir);
epoger@google.com5fd53852012-03-22 18:20:06 +0000572 comparisonPath.append(*comparisonFiles[j]);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000573
epoger@google.com5fd53852012-03-22 18:20:06 +0000574 DiffRecord *drp = NULL;
575 int v = strcmp(baseFiles[i]->c_str(),
576 comparisonFiles[j]->c_str());
577
578 if (v < 0) {
579 // in baseDir, but not in comparisonDir
580 drp = new DiffRecord(*baseFiles[i],
581 basePath, comparisonPath, false, true);
582 ++i;
583 } else if (v > 0) {
584 // in comparisonDir, but not in baseDir
585 drp = new DiffRecord(*comparisonFiles[j],
586 basePath, comparisonPath, true, false);
587 ++j;
588 } else {
589 // let's diff!
590 drp = new DiffRecord(*baseFiles[i], basePath, comparisonPath);
591
592 if (get_bitmaps(drp)) {
593 create_and_write_diff_image(drp, dmp, colorThreshold,
594 outputDir, *baseFiles[i]);
595 }
596
597 ++i;
598 ++j;
599 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000600
601 differences->push(drp);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000602 summary->add(drp);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000603 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000604
605 for (; i < baseFiles.count(); ++i) {
606 // files only in baseDir
607 SkString basePath (baseDir);
608 basePath.append(*baseFiles[i]);
609 SkString comparisonPath;
610 DiffRecord *drp = new DiffRecord(*baseFiles[i], basePath,
611 comparisonPath, false, true);
612 differences->push(drp);
613 summary->add(drp);
614 }
615
616 for (; j < comparisonFiles.count(); ++j) {
617 // files only in comparisonDir
618 SkString basePath;
619 SkString comparisonPath(comparisonDir);
620 comparisonPath.append(*comparisonFiles[j]);
621 DiffRecord *drp = new DiffRecord(*comparisonFiles[j], basePath,
622 comparisonPath, true, false);
623 differences->push(drp);
624 summary->add(drp);
625 }
626
627 release_file_list(&baseFiles);
628 release_file_list(&comparisonFiles);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000629}
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000630
tomhudson@google.com7d042802011-07-14 13:15:55 +0000631static void create_diff_images_chromium (DiffMetricProc dmp,
632 const int colorThreshold,
633 RecordArray* differences,
634 const SkString& dirname,
635 const SkString& outputDir,
636 DiffSummary* summary) {
637 SkOSFile::Iter baseIterator (dirname.c_str());
638 SkString filename;
639 while (baseIterator.next(&filename)) {
640 if (filename.endsWith(".pdf")) {
641 continue;
642 }
643 if (filename.endsWith("-expected.png")) {
644 SkString expectedPath (dirname);
645 expectedPath.append(filename);
646 SkString shortName (chrome_expected_name_to_short(filename));
647 SkString actualPath (chrome_expected_path_to_actual(expectedPath));
648 DiffRecord * drp =
649 new DiffRecord (shortName, expectedPath, actualPath);
650 if (!get_bitmaps(drp)) {
651 continue;
652 }
653 create_and_write_diff_image(drp, dmp, colorThreshold,
654 outputDir, shortName);
655
656 differences->push(drp);
657 summary->add(drp);
658 }
659 }
660}
661
662static void analyze_chromium(DiffMetricProc dmp,
663 const int colorThreshold,
664 RecordArray* differences,
665 const SkString& dirname,
666 const SkString& outputDir,
667 DiffSummary* summary) {
668 create_diff_images_chromium(dmp, colorThreshold, differences,
669 dirname, outputDir, summary);
670 SkOSFile::Iter dirIterator(dirname.c_str());
671 SkString newdirname;
672 while (dirIterator.next(&newdirname, true)) {
673 if (newdirname.startsWith(".")) {
674 continue;
675 }
676 SkString fullname (dirname);
677 fullname.append(newdirname);
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000678 if (!fullname.endsWith(PATH_DIV_STR)) {
679 fullname.append(PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000680 }
681 analyze_chromium(dmp, colorThreshold, differences,
682 fullname, outputDir, summary);
683 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000684}
685
686/// Make layout more consistent by scaling image to 240 height, 360 width,
687/// or natural size, whichever is smallest.
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000688static int compute_image_height (int height, int width) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000689 int retval = 240;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000690 if (height < retval) {
691 retval = height;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000692 }
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000693 float scale = (float) retval / height;
694 if (width * scale > 360) {
695 scale = (float) 360 / width;
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000696 retval = static_cast<int>(height * scale);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000697 }
698 return retval;
699}
700
epoger@google.com25d961c2012-02-02 20:50:36 +0000701static void print_table_header (SkFILEWStream* stream,
702 const int matchCount,
703 const int colorThreshold,
704 const RecordArray& differences,
705 const SkString &baseDir,
706 const SkString &comparisonDir) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000707 SkTime::DateTime dt;
708 SkTime::GetDateTime(&dt);
epoger@google.com25d961c2012-02-02 20:50:36 +0000709 stream->writeText("<table>\n");
710 stream->writeText("<tr><th>");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000711 stream->writeText("SkDiff run at ");
712 stream->writeDecAsText(dt.fHour);
713 stream->writeText(":");
714 if (dt.fMinute < 10) {
715 stream->writeText("0");
716 }
717 stream->writeDecAsText(dt.fMinute);
718 stream->writeText(":");
719 if (dt.fSecond < 10) {
720 stream->writeText("0");
721 }
722 stream->writeDecAsText(dt.fSecond);
723 stream->writeText("<br>");
724 stream->writeDecAsText(matchCount);
725 stream->writeText(" of ");
726 stream->writeDecAsText(differences.count());
727 stream->writeText(" images matched ");
728 if (colorThreshold == 0) {
729 stream->writeText("exactly");
730 } else {
731 stream->writeText("within ");
732 stream->writeDecAsText(colorThreshold);
733 stream->writeText(" color units per component");
734 }
735 stream->writeText(".<br>");
epoger@google.com25d961c2012-02-02 20:50:36 +0000736 stream->writeText("</th>\n<th>");
737 stream->writeText("every different pixel shown in white");
738 stream->writeText("</th>\n<th>");
739 stream->writeText("color difference at each pixel");
740 stream->writeText("</th>\n<th>");
741 stream->writeText(baseDir.c_str());
742 stream->writeText("</th>\n<th>");
743 stream->writeText(comparisonDir.c_str());
744 stream->writeText("</th>\n");
745 stream->writeText("</tr>\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000746}
747
748static void print_pixel_count (SkFILEWStream* stream,
749 const DiffRecord& diff) {
750 stream->writeText("<br>(");
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000751 stream->writeDecAsText(static_cast<int>(diff.fFractionDifference *
752 diff.fBaseWidth *
753 diff.fBaseHeight));
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000754 stream->writeText(" pixels)");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000755/*
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000756 stream->writeDecAsText(diff.fWeightedFraction *
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000757 diff.fBaseWidth *
758 diff.fBaseHeight);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000759 stream->writeText(" weighted pixels)");
760*/
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000761}
762
763static void print_label_cell (SkFILEWStream* stream,
764 const DiffRecord& diff) {
765 stream->writeText("<td>");
766 stream->writeText(diff.fFilename.c_str());
767 stream->writeText("<br>");
epoger@google.com5fd53852012-03-22 18:20:06 +0000768 if (diff.fBaseMissing || diff.fComparisonMissing) {
769 stream->writeText("</td>");
770 return;
771 }
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000772 if (diff.fDoImageSizesMismatch) {
773 stream->writeText("Image sizes differ");
774 stream->writeText("</td>");
775 return;
776 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000777 char metricBuf [20];
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000778 sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000779 stream->writeText(metricBuf);
780 stream->writeText(" of pixels differ");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000781 stream->writeText("\n (");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000782 sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000783 stream->writeText(metricBuf);
784 stream->writeText(" weighted)");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000785 // Write the actual number of pixels that differ if it's < 1%
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000786 if (diff.fFractionDifference < 0.01) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000787 print_pixel_count(stream, diff);
788 }
789 stream->writeText("<br>Average color mismatch ");
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000790 stream->writeDecAsText(static_cast<int>(MAX3(diff.fAverageMismatchR,
791 diff.fAverageMismatchG,
792 diff.fAverageMismatchB)));
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000793 stream->writeText("<br>Max color mismatch ");
794 stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
795 diff.fMaxMismatchG,
796 diff.fMaxMismatchB));
797 stream->writeText("</td>");
798}
799
800static void print_image_cell (SkFILEWStream* stream,
tomhudson@google.com4e305982011-07-13 17:42:46 +0000801 const SkString& path,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000802 int height) {
803 stream->writeText("<td><a href=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000804 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000805 stream->writeText("\"><img src=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000806 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000807 stream->writeText("\" height=\"");
808 stream->writeDecAsText(height);
809 stream->writeText("px\"></a></td>");
810}
811
epoger@google.com01f78702012-04-12 16:32:04 +0000812static void print_text_cell (SkFILEWStream* stream, const char* text) {
813 stream->writeText("<td align=center>");
814 if (NULL != text) {
815 stream->writeText(text);
816 }
817 stream->writeText("</td>");
818}
819
epoger@google.com5fd53852012-03-22 18:20:06 +0000820static void print_diff_with_missing_file(SkFILEWStream* stream,
821 DiffRecord& diff,
822 const SkString& relativePath) {
823 stream->writeText("<tr>\n");
824 print_label_cell(stream, diff);
825 stream->writeText("<td>N/A</td>");
826 stream->writeText("<td>N/A</td>");
827 if (!diff.fBaseMissing) {
828 int h, w;
829 if (!get_bitmap_height_width(diff.fBasePath, &h, &w)) {
830 stream->writeText("<td>N/A</td>");
831 } else {
832 int height = compute_image_height(h, w);
833 if (!diff.fBasePath.startsWith(PATH_DIV_STR)) {
834 diff.fBasePath.prepend(relativePath);
835 }
836 print_image_cell(stream, diff.fBasePath, height);
837 }
838 } else {
839 stream->writeText("<td>N/A</td>");
840 }
841 if (!diff.fComparisonMissing) {
842 int h, w;
843 if (!get_bitmap_height_width(diff.fComparisonPath, &h, &w)) {
844 stream->writeText("<td>N/A</td>");
845 } else {
846 int height = compute_image_height(h, w);
847 if (!diff.fComparisonPath.startsWith(PATH_DIV_STR)) {
848 diff.fComparisonPath.prepend(relativePath);
849 }
850 print_image_cell(stream, diff.fComparisonPath, height);
851 }
852 } else {
853 stream->writeText("<td>N/A</td>");
854 }
855 stream->writeText("</tr>\n");
856 stream->flush();
857}
858
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000859static void print_diff_page (const int matchCount,
860 const int colorThreshold,
861 const RecordArray& differences,
862 const SkString& baseDir,
863 const SkString& comparisonDir,
864 const SkString& outputDir) {
865
tomhudson@google.com5b325292011-05-24 19:41:13 +0000866 SkString outputPath (outputDir);
867 outputPath.append("index.html");
868 //SkFILEWStream outputStream ("index.html");
869 SkFILEWStream outputStream (outputPath.c_str());
870
tomhudson@google.com4e305982011-07-13 17:42:46 +0000871 // Need to convert paths from relative-to-cwd to relative-to-outputDir
tomhudson@google.com5b325292011-05-24 19:41:13 +0000872 // FIXME this doesn't work if there are '..' inside the outputDir
873 unsigned int ui;
874 SkString relativePath;
875 for (ui = 0; ui < outputDir.size(); ui++) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000876 if (outputDir[ui] == PATH_DIV_CHAR) {
877 relativePath.append(".." PATH_DIV_STR);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000878 }
879 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000880
881 outputStream.writeText("<html>\n<body>\n");
epoger@google.com25d961c2012-02-02 20:50:36 +0000882 print_table_header(&outputStream, matchCount, colorThreshold, differences,
883 baseDir, comparisonDir);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000884 int i;
885 for (i = 0; i < differences.count(); i++) {
886 DiffRecord* diff = differences[i];
epoger@google.com5fd53852012-03-22 18:20:06 +0000887
888 if (diff->fBaseMissing || diff->fComparisonMissing) {
889 print_diff_with_missing_file(&outputStream, *diff, relativePath);
890 continue;
891 } else if (0 == diff->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000892 continue;
893 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000894
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000895 if (!diff->fBasePath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000896 diff->fBasePath.prepend(relativePath);
897 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000898 if (!diff->fComparisonPath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000899 diff->fComparisonPath.prepend(relativePath);
900 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000901
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000902 int height = compute_image_height(diff->fBaseHeight, diff->fBaseWidth);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000903 outputStream.writeText("<tr>\n");
904 print_label_cell(&outputStream, *diff);
epoger@google.com01f78702012-04-12 16:32:04 +0000905 if (diff->fDoImageSizesMismatch) {
906 print_text_cell(&outputStream,
907 "[image size mismatch, so no diff to display]");
908 print_text_cell(&outputStream,
909 "[image size mismatch, so no diff to display]");
910 } else {
911 print_image_cell(&outputStream,
912 filename_to_white_filename(diff->fFilename), height);
913 print_image_cell(&outputStream,
914 filename_to_diff_filename(diff->fFilename), height);
915 }
epoger@google.com25d961c2012-02-02 20:50:36 +0000916 print_image_cell(&outputStream, diff->fBasePath, height);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000917 print_image_cell(&outputStream, diff->fComparisonPath, height);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000918 outputStream.writeText("</tr>\n");
919 outputStream.flush();
920 }
921 outputStream.writeText("</table>\n");
922 outputStream.writeText("</body>\n</html>\n");
923 outputStream.flush();
924}
925
926static void usage (char * argv0) {
927 SkDebugf("Skia baseline image diff tool\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000928 SkDebugf("Usage: %s baseDir comparisonDir [outputDir]\n", argv0);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000929 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000930" %s --chromium --release|--debug baseDir outputDir\n", argv0);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000931 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000932" --threshold n: only report differences > n (in one channel) [default 0]\n"
933" --sortbymismatch: sort by average color channel mismatch\n");
934 SkDebugf(
935" --sortbymaxmismatch: sort by worst color channel mismatch,\n"
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000936" break ties with -sortbymismatch,\n"
937" [default by fraction of pixels mismatching]\n");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000938 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000939" --weighted: sort by # pixels different weighted by color difference\n");
940 SkDebugf(
941" --chromium-release: process Webkit LayoutTests results instead of gm\n"
942" --chromium-debug: process Webkit LayoutTests results instead of gm\n");
943 SkDebugf(
944" baseDir: directory to read baseline images from,\n"
945" or chromium/src directory for --chromium.\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000946 SkDebugf(" comparisonDir: directory to read comparison images from\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000947 SkDebugf(
948" outputDir: directory to write difference images to; defaults to\n"
tomhudson@google.com7d042802011-07-14 13:15:55 +0000949" comparisonDir when not running --chromium\n");
950 SkDebugf("Also creates an \"index.html\" file in the output directory.\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000951}
952
953int main (int argc, char ** argv) {
954 DiffMetricProc diffProc = compute_diff_pmcolor;
955 SkQSortCompareProc sortProc = (SkQSortCompareProc) compare_diff_metrics;
956
957 // Maximum error tolerated in any one color channel in any one pixel before
958 // a difference is reported.
959 int colorThreshold = 0;
960 SkString baseDir;
961 SkString comparisonDir;
962 SkString outputDir;
963
tomhudson@google.com7d042802011-07-14 13:15:55 +0000964 bool analyzeChromium = false;
965 bool chromiumDebug = false;
966 bool chromiumRelease = false;
967
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000968 RecordArray differences;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000969 DiffSummary summary;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000970
971 int i, j;
972 for (i = 1, j = 0; i < argc; i++) {
tomhudson@google.com7d042802011-07-14 13:15:55 +0000973 if (!strcmp(argv[i], "--help")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000974 usage(argv[0]);
975 return 0;
976 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000977 if (!strcmp(argv[i], "--sortbymismatch")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000978 sortProc = (SkQSortCompareProc) compare_diff_mean_mismatches;
979 continue;
980 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000981 if (!strcmp(argv[i], "--sortbymaxmismatch")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000982 sortProc = (SkQSortCompareProc) compare_diff_max_mismatches;
983 continue;
984 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000985 if (!strcmp(argv[i], "--weighted")) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000986 sortProc = (SkQSortCompareProc) compare_diff_weighted;
987 continue;
988 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000989 if (!strcmp(argv[i], "--chromium-release")) {
990 analyzeChromium = true;
991 chromiumRelease = true;
992 continue;
993 }
994 if (!strcmp(argv[i], "--chromium-debug")) {
995 analyzeChromium = true;
996 chromiumDebug = true;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000997 continue;
998 }
999 if (argv[i][0] != '-') {
1000 switch (j++) {
1001 case 0:
1002 baseDir.set(argv[i]);
1003 continue;
1004 case 1:
1005 comparisonDir.set(argv[i]);
1006 continue;
1007 case 2:
1008 outputDir.set(argv[i]);
1009 continue;
1010 default:
1011 usage(argv[0]);
1012 return 0;
1013 }
1014 }
1015
1016 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
1017 usage(argv[0]);
1018 return 0;
1019 }
tomhudson@google.com7d042802011-07-14 13:15:55 +00001020 if (analyzeChromium) {
1021 if (j != 2) {
1022 usage(argv[0]);
1023 return 0;
1024 }
1025 if (chromiumRelease && chromiumDebug) {
1026 SkDebugf(
1027"--chromium must be either -release or -debug, not both!\n");
1028 return 0;
1029 }
1030 }
1031
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001032 if (j == 2) {
1033 outputDir = comparisonDir;
1034 } else if (j != 3) {
1035 usage(argv[0]);
1036 return 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001037 }
1038
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001039 if (!baseDir.endsWith(PATH_DIV_STR)) {
1040 baseDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001041 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001042 if (!comparisonDir.endsWith(PATH_DIV_STR)) {
1043 comparisonDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001044 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001045 if (!outputDir.endsWith(PATH_DIV_STR)) {
1046 outputDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001047 }
1048
tomhudson@google.com7d042802011-07-14 13:15:55 +00001049 if (analyzeChromium) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001050 baseDir.append("webkit" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001051 if (chromiumRelease) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001052 baseDir.append("Release" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001053 }
1054 if (chromiumDebug) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001055 baseDir.append("Debug" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001056 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001057 baseDir.append("layout-test-results" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001058 analyze_chromium(diffProc, colorThreshold, &differences,
1059 baseDir, outputDir, &summary);
1060 } else {
1061 create_diff_images(diffProc, colorThreshold, &differences,
1062 baseDir, comparisonDir, outputDir, &summary);
1063 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001064 summary.print();
tomhudson@google.com7d042802011-07-14 13:15:55 +00001065
1066 if (differences.count()) {
1067 SkQSort(differences.begin(), differences.count(),
1068 sizeof(DiffRecord*), sortProc);
1069 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001070 print_diff_page(summary.fNumMatches, colorThreshold, differences,
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001071 baseDir, comparisonDir, outputDir);
1072
1073 for (i = 0; i < differences.count(); i++) {
1074 delete differences[i];
1075 }
1076}