blob: d70027aaf55c7202e55d1052450443e0df1eedba [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"
epoger@google.com46256ea2012-05-22 13:45:35 +00009#include "SkData.h"
tomhudson@google.com4b33d282011-04-27 15:39:30 +000010#include "SkImageDecoder.h"
11#include "SkImageEncoder.h"
12#include "SkOSFile.h"
13#include "SkStream.h"
14#include "SkTDArray.h"
15#include "SkTemplates.h"
16#include "SkTime.h"
17#include "SkTSearch.h"
18#include "SkTypes.h"
19
20/**
21 * skdiff
22 *
23 * Given three directory names, expects to find identically-named files in
24 * each of the first two; the first are treated as a set of baseline,
25 * the second a set of variant images, and a diff image is written into the
26 * third directory for each pair.
tomhudson@google.com7d042802011-07-14 13:15:55 +000027 * Creates an index.html in the current third directory to compare each
tomhudson@google.com4b33d282011-04-27 15:39:30 +000028 * pair that does not match exactly.
29 * Does *not* recursively descend directories.
epoger@google.combe6188d2012-05-31 15:13:45 +000030 *
31 * Returns zero exit code if all images match across baseDir and comparisonDir.
tomhudson@google.com4b33d282011-04-27 15:39:30 +000032 */
33
bsalomon@google.com1a315fe2011-09-23 14:56:37 +000034#if SK_BUILD_FOR_WIN32
35 #define PATH_DIV_STR "\\"
36 #define PATH_DIV_CHAR '\\'
37#else
38 #define PATH_DIV_STR "/"
39 #define PATH_DIV_CHAR '/'
40#endif
41
epoger@google.com292aff62012-05-16 14:57:28 +000042// Result of comparison for each pair of files.
epoger@google.com28060e72012-06-28 16:47:34 +000043// Listed from "better" to "worse", for sorting of results.
epoger@google.com292aff62012-05-16 14:57:28 +000044enum Result {
epoger@google.com46a45962012-07-12 18:16:02 +000045 kEqualBits,
46 kEqualPixels,
47 kDifferentPixels,
48 kDifferentSizes,
49 kDifferentOther,
50 kComparisonMissing,
51 kBaseMissing,
52 kUnknown,
epoger@google.com76222c02012-05-31 15:12:09 +000053 //
54 kNumResultTypes // NOT A VALID VALUE--used to set up arrays. Must be last.
epoger@google.com292aff62012-05-16 14:57:28 +000055};
56
epoger@google.com46a45962012-07-12 18:16:02 +000057// Returns a text description of the given Result type.
58const char *getResultDescription(Result result) {
59 switch (result) {
60 case kEqualBits:
61 return "contain exactly the same bits";
62 case kEqualPixels:
63 return "contain the same pixel values, but not the same bits";
64 case kDifferentPixels:
65 return "have identical dimensions but some differing pixels";
66 case kDifferentSizes:
67 return "have differing dimensions";
68 case kDifferentOther:
69 return "contain different bits and are not parsable images";
70 case kBaseMissing:
71 return "missing from baseDir";
72 case kComparisonMissing:
73 return "missing from comparisonDir";
74 case kUnknown:
75 return "not compared yet";
76 default:
77 return NULL;
78 }
79}
80
tomhudson@google.com4b33d282011-04-27 15:39:30 +000081struct DiffRecord {
tomhudson@google.com4e305982011-07-13 17:42:46 +000082 DiffRecord (const SkString filename,
83 const SkString basePath,
epoger@google.com5fd53852012-03-22 18:20:06 +000084 const SkString comparisonPath,
epoger@google.com292aff62012-05-16 14:57:28 +000085 const Result result = kUnknown)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000086 : fFilename (filename)
tomhudson@google.com4e305982011-07-13 17:42:46 +000087 , fBasePath (basePath)
88 , fComparisonPath (comparisonPath)
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000089 , fBaseBitmap (new SkBitmap ())
90 , fComparisonBitmap (new SkBitmap ())
91 , fDifferenceBitmap (new SkBitmap ())
epoger@google.com25d961c2012-02-02 20:50:36 +000092 , fWhiteBitmap (new SkBitmap ())
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000093 , fBaseHeight (0)
94 , fBaseWidth (0)
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000095 , fFractionDifference (0)
96 , fWeightedFraction (0)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000097 , fAverageMismatchR (0)
98 , fAverageMismatchG (0)
99 , fAverageMismatchB (0)
100 , fMaxMismatchR (0)
101 , fMaxMismatchG (0)
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000102 , fMaxMismatchB (0)
epoger@google.com292aff62012-05-16 14:57:28 +0000103 , fResult(result) {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000104 };
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000105
106 SkString fFilename;
tomhudson@google.com4e305982011-07-13 17:42:46 +0000107 SkString fBasePath;
108 SkString fComparisonPath;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000109
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000110 SkBitmap* fBaseBitmap;
111 SkBitmap* fComparisonBitmap;
112 SkBitmap* fDifferenceBitmap;
epoger@google.com25d961c2012-02-02 20:50:36 +0000113 SkBitmap* fWhiteBitmap;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000114
115 int fBaseHeight;
116 int fBaseWidth;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000117
118 /// Arbitrary floating-point metric to be used to sort images from most
119 /// to least different from baseline; values of 0 will be omitted from the
120 /// summary webpage.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000121 float fFractionDifference;
122 float fWeightedFraction;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000123
124 float fAverageMismatchR;
125 float fAverageMismatchG;
126 float fAverageMismatchB;
127
128 uint32_t fMaxMismatchR;
129 uint32_t fMaxMismatchG;
130 uint32_t fMaxMismatchB;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000131
epoger@google.com292aff62012-05-16 14:57:28 +0000132 /// Which category of diff result.
133 Result fResult;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000134};
135
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000136#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
137#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
138
epoger@google.com25d961c2012-02-02 20:50:36 +0000139const SkPMColor PMCOLOR_WHITE = SkPreMultiplyColor(SK_ColorWHITE);
140const SkPMColor PMCOLOR_BLACK = SkPreMultiplyColor(SK_ColorBLACK);
141
epoger@google.coma5f406e2012-05-01 13:26:16 +0000142typedef SkTDArray<SkString*> StringArray;
143typedef StringArray FileArray;
epoger@google.com5fd53852012-03-22 18:20:06 +0000144
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000145struct DiffSummary {
146 DiffSummary ()
147 : fNumMatches (0)
148 , fNumMismatches (0)
149 , fMaxMismatchV (0)
150 , fMaxMismatchPercent (0) { };
151
epoger@google.com5fd53852012-03-22 18:20:06 +0000152 ~DiffSummary() {
epoger@google.com76222c02012-05-31 15:12:09 +0000153 for (int i = 0; i < kNumResultTypes; i++) {
154 fResultsOfType[i].deleteAll();
155 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000156 }
157
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000158 uint32_t fNumMatches;
159 uint32_t fNumMismatches;
160 uint32_t fMaxMismatchV;
161 float fMaxMismatchPercent;
162
epoger@google.com76222c02012-05-31 15:12:09 +0000163 FileArray fResultsOfType[kNumResultTypes];
164
epoger@google.com46a45962012-07-12 18:16:02 +0000165 // Print a line about the contents of this FileArray to stdout; if the FileArray is empty,
166 // print nothing.
167 void printContents(const FileArray& fileArray, const char* headerText, bool listFilenames) {
epoger@google.com76222c02012-05-31 15:12:09 +0000168 int n = fileArray.count();
169 if (n > 0) {
epoger@google.com46a45962012-07-12 18:16:02 +0000170 printf(" %d file pairs %s", n, headerText);
171 if (listFilenames) {
172 printf(": ");
173 for (int i = 0; i < n; ++i) {
174 printf("%s ", fileArray[i]->c_str());
175 }
epoger@google.com76222c02012-05-31 15:12:09 +0000176 }
epoger@google.com46a45962012-07-12 18:16:02 +0000177 printf("\n");
epoger@google.com76222c02012-05-31 15:12:09 +0000178 }
179 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000180
epoger@google.com46a45962012-07-12 18:16:02 +0000181 void print(bool listFilenames) {
182 printf("compared %d file pairs:\n", fNumMatches + fNumMismatches);
183 for (int resultInt = 0; resultInt < kNumResultTypes; resultInt++) {
184 Result result = static_cast<Result>(resultInt);
185 printContents(fResultsOfType[result], getResultDescription(result), listFilenames);
186 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000187 if (fNumMismatches > 0) {
188 printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
epoger@google.com46a45962012-07-12 18:16:02 +0000189 printf("Largest area mismatch was %.2f%% of pixels\n",fMaxMismatchPercent);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000190 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000191 }
192
193 void add (DiffRecord* drp) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000194 uint32_t mismatchValue;
epoger@google.com292aff62012-05-16 14:57:28 +0000195
epoger@google.com76222c02012-05-31 15:12:09 +0000196 fResultsOfType[drp->fResult].push(new SkString(drp->fFilename));
epoger@google.com292aff62012-05-16 14:57:28 +0000197 switch (drp->fResult) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000198 case kEqualBits:
199 fNumMatches++;
200 break;
epoger@google.com292aff62012-05-16 14:57:28 +0000201 case kEqualPixels:
202 fNumMatches++;
203 break;
epoger@google.com46256ea2012-05-22 13:45:35 +0000204 case kDifferentSizes:
epoger@google.com5fd53852012-03-22 18:20:06 +0000205 fNumMismatches++;
epoger@google.com292aff62012-05-16 14:57:28 +0000206 break;
epoger@google.com46256ea2012-05-22 13:45:35 +0000207 case kDifferentPixels:
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000208 fNumMismatches++;
209 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
210 fMaxMismatchPercent = drp->fFractionDifference * 100;
211 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000212 mismatchValue = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
213 drp->fMaxMismatchB);
214 if (mismatchValue > fMaxMismatchV) {
215 fMaxMismatchV = mismatchValue;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000216 }
epoger@google.com292aff62012-05-16 14:57:28 +0000217 break;
epoger@google.com46256ea2012-05-22 13:45:35 +0000218 case kDifferentOther:
219 fNumMismatches++;
epoger@google.com46256ea2012-05-22 13:45:35 +0000220 break;
221 case kBaseMissing:
222 fNumMismatches++;
epoger@google.com46256ea2012-05-22 13:45:35 +0000223 break;
224 case kComparisonMissing:
225 fNumMismatches++;
epoger@google.com46256ea2012-05-22 13:45:35 +0000226 break;
227 case kUnknown:
228 SkDEBUGFAIL("adding uncategorized DiffRecord");
229 break;
230 default:
231 SkDEBUGFAIL("adding DiffRecord with unhandled fResult value");
232 break;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000233 }
234 }
235};
236
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000237typedef SkTDArray<DiffRecord*> RecordArray;
238
epoger@google.com28060e72012-06-28 16:47:34 +0000239/// A wrapper for any sortProc (comparison routine) which applies a first-order
240/// sort beforehand, and a tiebreaker if the sortProc returns 0.
241template<typename T>
242static int compare(const void* untyped_lhs, const void* untyped_rhs) {
243 const DiffRecord* lhs = *reinterpret_cast<DiffRecord* const*>(untyped_lhs);
244 const DiffRecord* rhs = *reinterpret_cast<DiffRecord* const*>(untyped_rhs);
245
246 // First-order sort... these comparisons should be applied before comparing
247 // pixel values, no matter what.
248 if (lhs->fResult != rhs->fResult) {
249 return (lhs->fResult < rhs->fResult) ? 1 : -1;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000250 }
epoger@google.com28060e72012-06-28 16:47:34 +0000251
252 // Passed first-order sort, so call the pixel comparison routine.
253 int result = T::comparePixels(lhs, rhs);
254 if (result != 0) {
255 return result;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000256 }
epoger@google.com28060e72012-06-28 16:47:34 +0000257
258 // Tiebreaker... if we got to this point, we don't really care
259 // which order they are sorted in, but let's at least be consistent.
260 return strcmp(lhs->fFilename.c_str(), rhs->fFilename.c_str());
tomhudson@google.com5b325292011-05-24 19:41:13 +0000261}
262
epoger@google.com28060e72012-06-28 16:47:34 +0000263/// Comparison routine for qsort; sorts by fFractionDifference
264/// from largest to smallest.
265class CompareDiffMetrics {
266public:
267 static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
268 if (lhs->fFractionDifference < rhs->fFractionDifference) {
269 return 1;
270 }
271 if (rhs->fFractionDifference < lhs->fFractionDifference) {
272 return -1;
273 }
274 return 0;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000275 }
epoger@google.com28060e72012-06-28 16:47:34 +0000276};
277
278class CompareDiffWeighted {
279public:
280 static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
281 if (lhs->fWeightedFraction < rhs->fWeightedFraction) {
282 return 1;
283 }
284 if (lhs->fWeightedFraction > rhs->fWeightedFraction) {
285 return -1;
286 }
287 return 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000288 }
epoger@google.com28060e72012-06-28 16:47:34 +0000289};
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000290
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000291/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
292/// from largest to smallest.
epoger@google.com28060e72012-06-28 16:47:34 +0000293class CompareDiffMeanMismatches {
294public:
295 static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
296 float leftValue = MAX3(lhs->fAverageMismatchR,
297 lhs->fAverageMismatchG,
298 lhs->fAverageMismatchB);
299 float rightValue = MAX3(rhs->fAverageMismatchR,
300 rhs->fAverageMismatchG,
301 rhs->fAverageMismatchB);
302 if (leftValue < rightValue) {
303 return 1;
304 }
305 if (rightValue < leftValue) {
306 return -1;
307 }
308 return 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000309 }
epoger@google.com28060e72012-06-28 16:47:34 +0000310};
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000311
312/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB})
313/// from largest to smallest.
epoger@google.com28060e72012-06-28 16:47:34 +0000314class CompareDiffMaxMismatches {
315public:
316 static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
317 uint32_t leftValue = MAX3(lhs->fMaxMismatchR,
318 lhs->fMaxMismatchG,
319 lhs->fMaxMismatchB);
320 uint32_t rightValue = MAX3(rhs->fMaxMismatchR,
321 rhs->fMaxMismatchG,
322 rhs->fMaxMismatchB);
323 if (leftValue < rightValue) {
324 return 1;
325 }
326 if (rightValue < leftValue) {
327 return -1;
328 }
329
330 return CompareDiffMeanMismatches::comparePixels(lhs, rhs);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000331 }
epoger@google.com28060e72012-06-28 16:47:34 +0000332};
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000333
334
335
336/// Parameterized routine to compute the color of a pixel in a difference image.
337typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
338
caryclark@google.com3dd45912012-06-06 12:11:10 +0000339#if 0 // UNUSED
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000340static void expand_and_copy (int width, int height, SkBitmap** dest) {
341 SkBitmap* temp = new SkBitmap ();
342 temp->reset();
343 temp->setConfig((*dest)->config(), width, height);
344 temp->allocPixels();
345 (*dest)->copyPixelsTo(temp->getPixels(), temp->getSize(),
346 temp->rowBytes());
347 *dest = temp;
348}
caryclark@google.com3dd45912012-06-06 12:11:10 +0000349#endif
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000350
epoger@google.com46256ea2012-05-22 13:45:35 +0000351/// Returns true if the two buffers passed in are both non-NULL, and include
352/// exactly the same byte values (and identical lengths).
353static bool are_buffers_equal(SkData* skdata1, SkData* skdata2) {
354 if ((NULL == skdata1) || (NULL == skdata2)) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000355 return false;
356 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000357 if (skdata1->size() != skdata2->size()) {
358 return false;
359 }
360 return (0 == memcmp(skdata1->data(), skdata2->data(), skdata1->size()));
361}
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000362
epoger@google.com46256ea2012-05-22 13:45:35 +0000363/// Reads the file at the given path and returns its complete contents as an
364/// SkData object (or returns NULL on error).
365static SkData* read_file(const char* file_path) {
366 SkFILEStream fileStream(file_path);
367 if (!fileStream.isValid()) {
368 SkDebugf("WARNING: could not open file <%s> for reading\n", file_path);
369 return NULL;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000370 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000371 size_t bytesInFile = fileStream.getLength();
372 size_t bytesLeftToRead = bytesInFile;
373
374 void* bufferStart = sk_malloc_throw(bytesInFile);
375 char* bufferPointer = (char*)bufferStart;
376 while (bytesLeftToRead > 0) {
377 size_t bytesReadThisTime = fileStream.read(
378 bufferPointer, bytesLeftToRead);
379 if (0 == bytesReadThisTime) {
380 SkDebugf("WARNING: error reading from <%s>\n", file_path);
381 sk_free(bufferStart);
382 return NULL;
383 }
384 bytesLeftToRead -= bytesReadThisTime;
385 bufferPointer += bytesReadThisTime;
386 }
387 return SkData::NewFromMalloc(bufferStart, bytesInFile);
388}
389
390/// Decodes binary contents of baseFile and comparisonFile into
391/// diffRecord->fBaseBitmap and diffRecord->fComparisonBitmap.
392/// Returns true if that succeeds.
393static bool get_bitmaps (SkData* baseFileContents,
394 SkData* comparisonFileContents,
395 DiffRecord* diffRecord) {
396 SkMemoryStream compareStream(comparisonFileContents->data(),
397 comparisonFileContents->size());
398 SkMemoryStream baseStream(baseFileContents->data(),
399 baseFileContents->size());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000400
401 SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
402 if (NULL == codec) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000403 SkDebugf("ERROR: no codec found for basePath <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000404 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000405 return false;
406 }
407
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000408 // In debug, the DLL will automatically be unloaded when this is deleted,
409 // but that shouldn't be a problem in release mode.
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000410 SkAutoTDelete<SkImageDecoder> ad(codec);
411
412 baseStream.rewind();
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000413 if (!codec->decode(&baseStream, diffRecord->fBaseBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000414 SkBitmap::kARGB_8888_Config,
415 SkImageDecoder::kDecodePixels_Mode)) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000416 SkDebugf("ERROR: codec failed for basePath <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000417 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000418 return false;
419 }
420
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000421 diffRecord->fBaseWidth = diffRecord->fBaseBitmap->width();
422 diffRecord->fBaseHeight = diffRecord->fBaseBitmap->height();
423
424 if (!codec->decode(&compareStream, diffRecord->fComparisonBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000425 SkBitmap::kARGB_8888_Config,
426 SkImageDecoder::kDecodePixels_Mode)) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000427 SkDebugf("ERROR: codec failed for comparisonPath <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000428 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000429 return false;
430 }
431
432 return true;
433}
434
epoger@google.com5fd53852012-03-22 18:20:06 +0000435static bool get_bitmap_height_width(const SkString& path,
436 int *height, int *width) {
437 SkFILEStream stream(path.c_str());
438 if (!stream.isValid()) {
439 SkDebugf("ERROR: couldn't open file <%s>\n",
440 path.c_str());
441 return false;
442 }
443
444 SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
445 if (NULL == codec) {
446 SkDebugf("ERROR: no codec found for <%s>\n",
447 path.c_str());
448 return false;
449 }
450
451 SkAutoTDelete<SkImageDecoder> ad(codec);
452 SkBitmap bm;
453
454 stream.rewind();
455 if (!codec->decode(&stream, &bm,
456 SkBitmap::kARGB_8888_Config,
457 SkImageDecoder::kDecodePixels_Mode)) {
458 SkDebugf("ERROR: codec failed for <%s>\n",
459 path.c_str());
460 return false;
461 }
462
463 *height = bm.height();
464 *width = bm.width();
465
466 return true;
467}
468
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000469// from gm - thanks to PNG, we need to force all pixels 100% opaque
470static void force_all_opaque(const SkBitmap& bitmap) {
471 SkAutoLockPixels lock(bitmap);
472 for (int y = 0; y < bitmap.height(); y++) {
473 for (int x = 0; x < bitmap.width(); x++) {
474 *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
475 }
476 }
477}
478
479// from gm
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000480static bool write_bitmap(const SkString& path, const SkBitmap* bitmap) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000481 SkBitmap copy;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000482 bitmap->copyTo(&copy, SkBitmap::kARGB_8888_Config);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000483 force_all_opaque(copy);
484 return SkImageEncoder::EncodeFile(path.c_str(), copy,
485 SkImageEncoder::kPNG_Type, 100);
486}
487
488// from gm
489static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
490 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
491 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
492 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
493
494 return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
495}
496
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000497static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1,
498 const int threshold) {
499 int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
500 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
501 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
502 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
503
504 return ((SkAbs32(da) <= threshold) &&
505 (SkAbs32(dr) <= threshold) &&
506 (SkAbs32(dg) <= threshold) &&
507 (SkAbs32(db) <= threshold));
508}
509
510// based on gm
epoger@google.com66008522012-05-16 17:40:57 +0000511// Postcondition: when we exit this method, dr->fResult should have some value
512// other than kUnknown.
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000513static void compute_diff(DiffRecord* dr,
514 DiffMetricProc diffFunction,
515 const int colorThreshold) {
epoger@google.com25d961c2012-02-02 20:50:36 +0000516 SkAutoLockPixels alpDiff(*dr->fDifferenceBitmap);
517 SkAutoLockPixels alpWhite(*dr->fWhiteBitmap);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000518
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000519 const int w = dr->fComparisonBitmap->width();
520 const int h = dr->fComparisonBitmap->height();
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000521 int mismatchedPixels = 0;
522 int totalMismatchR = 0;
523 int totalMismatchG = 0;
524 int totalMismatchB = 0;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000525
526 if (w != dr->fBaseWidth || h != dr->fBaseHeight) {
epoger@google.com292aff62012-05-16 14:57:28 +0000527 dr->fResult = kDifferentSizes;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000528 return;
529 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000530 // Accumulate fractionally different pixels, then divide out
531 // # of pixels at the end.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000532 dr->fWeightedFraction = 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000533 for (int y = 0; y < h; y++) {
534 for (int x = 0; x < w; x++) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000535 SkPMColor c0 = *dr->fBaseBitmap->getAddr32(x, y);
536 SkPMColor c1 = *dr->fComparisonBitmap->getAddr32(x, y);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000537 SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
538 SkPMColor outputDifference = diffFunction(c0, c1);
539 uint32_t thisR = SkGetPackedR32(trueDifference);
540 uint32_t thisG = SkGetPackedG32(trueDifference);
541 uint32_t thisB = SkGetPackedB32(trueDifference);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000542 totalMismatchR += thisR;
543 totalMismatchG += thisG;
544 totalMismatchB += thisB;
545 // In HSV, value is defined as max RGB component.
546 int value = MAX3(thisR, thisG, thisB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000547 dr->fWeightedFraction += ((float) value) / 255;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000548 if (thisR > dr->fMaxMismatchR) {
549 dr->fMaxMismatchR = thisR;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000550 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000551 if (thisG > dr->fMaxMismatchG) {
552 dr->fMaxMismatchG = thisG;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000553 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000554 if (thisB > dr->fMaxMismatchB) {
555 dr->fMaxMismatchB = thisB;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000556 }
557 if (!colors_match_thresholded(c0, c1, colorThreshold)) {
558 mismatchedPixels++;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000559 *dr->fDifferenceBitmap->getAddr32(x, y) = outputDifference;
epoger@google.com25d961c2012-02-02 20:50:36 +0000560 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_WHITE;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000561 } else {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000562 *dr->fDifferenceBitmap->getAddr32(x, y) = 0;
epoger@google.com25d961c2012-02-02 20:50:36 +0000563 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_BLACK;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000564 }
565 }
566 }
epoger@google.com292aff62012-05-16 14:57:28 +0000567 if (0 == mismatchedPixels) {
568 dr->fResult = kEqualPixels;
569 return;
570 }
571 dr->fResult = kDifferentPixels;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000572 int pixelCount = w * h;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000573 dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
574 dr->fWeightedFraction /= pixelCount;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000575 dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
576 dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
577 dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000578}
579
epoger@google.com25d961c2012-02-02 20:50:36 +0000580static SkString filename_to_derived_filename (const SkString& filename,
581 const char *suffix) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000582 SkString diffName (filename);
583 const char* cstring = diffName.c_str();
584 int dotOffset = strrchr(cstring, '.') - cstring;
585 diffName.remove(dotOffset, diffName.size() - dotOffset);
epoger@google.com25d961c2012-02-02 20:50:36 +0000586 diffName.append(suffix);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000587 return diffName;
588}
589
epoger@google.com25d961c2012-02-02 20:50:36 +0000590/// Given a image filename, returns the name of the file containing the
591/// associated difference image.
592static SkString filename_to_diff_filename (const SkString& filename) {
593 return filename_to_derived_filename(filename, "-diff.png");
594}
595
596/// Given a image filename, returns the name of the file containing the
597/// "white" difference image.
598static SkString filename_to_white_filename (const SkString& filename) {
599 return filename_to_derived_filename(filename, "-white.png");
600}
601
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000602static void release_bitmaps(DiffRecord* drp) {
603 delete drp->fBaseBitmap;
604 drp->fBaseBitmap = NULL;
605 delete drp->fComparisonBitmap;
606 drp->fComparisonBitmap = NULL;
607 delete drp->fDifferenceBitmap;
608 drp->fDifferenceBitmap = NULL;
epoger@google.com25d961c2012-02-02 20:50:36 +0000609 delete drp->fWhiteBitmap;
610 drp->fWhiteBitmap = NULL;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000611}
612
tomhudson@google.com7d042802011-07-14 13:15:55 +0000613
epoger@google.coma5f406e2012-05-01 13:26:16 +0000614/// If outputDir.isEmpty(), don't write out diff files.
tomhudson@google.com7d042802011-07-14 13:15:55 +0000615static void create_and_write_diff_image(DiffRecord* drp,
616 DiffMetricProc dmp,
617 const int colorThreshold,
618 const SkString& outputDir,
619 const SkString& filename) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000620 const int w = drp->fBaseWidth;
621 const int h = drp->fBaseHeight;
622 drp->fDifferenceBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
623 drp->fDifferenceBitmap->allocPixels();
epoger@google.com25d961c2012-02-02 20:50:36 +0000624 drp->fWhiteBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
625 drp->fWhiteBitmap->allocPixels();
tomhudson@google.com7d042802011-07-14 13:15:55 +0000626
epoger@google.com66008522012-05-16 17:40:57 +0000627 SkASSERT(kUnknown == drp->fResult);
628 compute_diff(drp, dmp, colorThreshold);
629 SkASSERT(kUnknown != drp->fResult);
630
631 if ((kDifferentPixels == drp->fResult) && !outputDir.isEmpty()) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000632 SkString differencePath (outputDir);
633 differencePath.append(filename_to_diff_filename(filename));
634 write_bitmap(differencePath, drp->fDifferenceBitmap);
635 SkString whitePath (outputDir);
636 whitePath.append(filename_to_white_filename(filename));
637 write_bitmap(whitePath, drp->fWhiteBitmap);
638 }
epoger@google.com66008522012-05-16 17:40:57 +0000639
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000640 release_bitmaps(drp);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000641}
642
epoger@google.coma5f406e2012-05-01 13:26:16 +0000643/// Returns true if string contains any of these substrings.
644static bool string_contains_any_of(const SkString& string,
645 const StringArray& substrings) {
646 for (int i = 0; i < substrings.count(); i++) {
647 if (string.contains(substrings[i]->c_str())) {
648 return true;
649 }
650 }
651 return false;
652}
653
654/// Iterate over dir and get all files that:
655/// - match any of the substrings in matchSubstrings, but...
656/// - DO NOT match any of the substrings in nomatchSubstrings
657/// Returns the list of files in *files.
658static void get_file_list(const SkString& dir,
659 const StringArray& matchSubstrings,
660 const StringArray& nomatchSubstrings,
661 FileArray *files) {
epoger@google.com5fd53852012-03-22 18:20:06 +0000662 SkOSFile::Iter it(dir.c_str());
663 SkString filename;
664 while (it.next(&filename)) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000665 if (string_contains_any_of(filename, matchSubstrings) &&
666 !string_contains_any_of(filename, nomatchSubstrings)) {
667 files->push(new SkString(filename));
epoger@google.com5fd53852012-03-22 18:20:06 +0000668 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000669 }
670}
671
672static void release_file_list(FileArray *files) {
673 files->deleteAll();
674}
675
676/// Comparison routines for qsort, sort by file names.
677static int compare_file_name_metrics(SkString **lhs, SkString **rhs) {
678 return strcmp((*lhs)->c_str(), (*rhs)->c_str());
679}
680
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000681/// Creates difference images, returns the number that have a 0 metric.
epoger@google.coma5f406e2012-05-01 13:26:16 +0000682/// If outputDir.isEmpty(), don't write out diff files.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000683static void create_diff_images (DiffMetricProc dmp,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000684 const int colorThreshold,
685 RecordArray* differences,
686 const SkString& baseDir,
687 const SkString& comparisonDir,
688 const SkString& outputDir,
epoger@google.coma5f406e2012-05-01 13:26:16 +0000689 const StringArray& matchSubstrings,
690 const StringArray& nomatchSubstrings,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000691 DiffSummary* summary) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000692 SkASSERT(!baseDir.isEmpty());
693 SkASSERT(!comparisonDir.isEmpty());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000694
epoger@google.com5fd53852012-03-22 18:20:06 +0000695 FileArray baseFiles;
696 FileArray comparisonFiles;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000697
epoger@google.coma5f406e2012-05-01 13:26:16 +0000698 get_file_list(baseDir, matchSubstrings, nomatchSubstrings, &baseFiles);
699 get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings,
700 &comparisonFiles);
epoger@google.com5fd53852012-03-22 18:20:06 +0000701
epoger@google.coma5f406e2012-05-01 13:26:16 +0000702 if (!baseFiles.isEmpty()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +0000703 qsort(baseFiles.begin(), baseFiles.count(), sizeof(SkString*),
704 SkCastForQSort(compare_file_name_metrics));
epoger@google.coma5f406e2012-05-01 13:26:16 +0000705 }
706 if (!comparisonFiles.isEmpty()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +0000707 qsort(comparisonFiles.begin(), comparisonFiles.count(),
708 sizeof(SkString*), SkCastForQSort(compare_file_name_metrics));
epoger@google.coma5f406e2012-05-01 13:26:16 +0000709 }
epoger@google.com66008522012-05-16 17:40:57 +0000710
epoger@google.com5fd53852012-03-22 18:20:06 +0000711 int i = 0;
712 int j = 0;
713
714 while (i < baseFiles.count() &&
715 j < comparisonFiles.count()) {
716
tomhudson@google.com4e305982011-07-13 17:42:46 +0000717 SkString basePath (baseDir);
epoger@google.com5fd53852012-03-22 18:20:06 +0000718 basePath.append(*baseFiles[i]);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000719 SkString comparisonPath (comparisonDir);
epoger@google.com5fd53852012-03-22 18:20:06 +0000720 comparisonPath.append(*comparisonFiles[j]);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000721
epoger@google.com5fd53852012-03-22 18:20:06 +0000722 DiffRecord *drp = NULL;
723 int v = strcmp(baseFiles[i]->c_str(),
724 comparisonFiles[j]->c_str());
725
726 if (v < 0) {
727 // in baseDir, but not in comparisonDir
epoger@google.com292aff62012-05-16 14:57:28 +0000728 drp = new DiffRecord(*baseFiles[i], basePath, comparisonPath,
729 kComparisonMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000730 ++i;
731 } else if (v > 0) {
732 // in comparisonDir, but not in baseDir
epoger@google.com292aff62012-05-16 14:57:28 +0000733 drp = new DiffRecord(*comparisonFiles[j], basePath, comparisonPath,
734 kBaseMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000735 ++j;
736 } else {
epoger@google.com46256ea2012-05-22 13:45:35 +0000737 // Found the same filename in both baseDir and comparisonDir.
epoger@google.com5fd53852012-03-22 18:20:06 +0000738 drp = new DiffRecord(*baseFiles[i], basePath, comparisonPath);
epoger@google.com46256ea2012-05-22 13:45:35 +0000739 SkASSERT(kUnknown == drp->fResult);
epoger@google.com5fd53852012-03-22 18:20:06 +0000740
epoger@google.com46256ea2012-05-22 13:45:35 +0000741 SkData* baseFileBits;
742 SkData* comparisonFileBits;
743 if (NULL == (baseFileBits = read_file(basePath.c_str()))) {
744 SkDebugf("WARNING: couldn't read base file <%s>\n",
745 basePath.c_str());
746 drp->fResult = kBaseMissing;
747 } else if (NULL == (comparisonFileBits = read_file(
748 comparisonPath.c_str()))) {
749 SkDebugf("WARNING: couldn't read comparison file <%s>\n",
750 comparisonPath.c_str());
751 drp->fResult = kComparisonMissing;
752 } else {
753 if (are_buffers_equal(baseFileBits, comparisonFileBits)) {
754 drp->fResult = kEqualBits;
755 } else if (get_bitmaps(baseFileBits, comparisonFileBits, drp)) {
756 create_and_write_diff_image(drp, dmp, colorThreshold,
757 outputDir, *baseFiles[i]);
758 } else {
759 drp->fResult = kDifferentOther;
760 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000761 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000762 if (baseFileBits) {
763 baseFileBits->unref();
764 }
765 if (comparisonFileBits) {
766 comparisonFileBits->unref();
767 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000768 ++i;
769 ++j;
770 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000771 SkASSERT(kUnknown != drp->fResult);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000772 differences->push(drp);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000773 summary->add(drp);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000774 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000775
776 for (; i < baseFiles.count(); ++i) {
777 // files only in baseDir
778 SkString basePath (baseDir);
779 basePath.append(*baseFiles[i]);
780 SkString comparisonPath;
781 DiffRecord *drp = new DiffRecord(*baseFiles[i], basePath,
epoger@google.com292aff62012-05-16 14:57:28 +0000782 comparisonPath, kComparisonMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000783 differences->push(drp);
784 summary->add(drp);
785 }
786
787 for (; j < comparisonFiles.count(); ++j) {
788 // files only in comparisonDir
789 SkString basePath;
790 SkString comparisonPath(comparisonDir);
791 comparisonPath.append(*comparisonFiles[j]);
792 DiffRecord *drp = new DiffRecord(*comparisonFiles[j], basePath,
epoger@google.com292aff62012-05-16 14:57:28 +0000793 comparisonPath, kBaseMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000794 differences->push(drp);
795 summary->add(drp);
796 }
797
798 release_file_list(&baseFiles);
799 release_file_list(&comparisonFiles);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000800}
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000801
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000802/// Make layout more consistent by scaling image to 240 height, 360 width,
803/// or natural size, whichever is smallest.
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000804static int compute_image_height (int height, int width) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000805 int retval = 240;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000806 if (height < retval) {
807 retval = height;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000808 }
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000809 float scale = (float) retval / height;
810 if (width * scale > 360) {
811 scale = (float) 360 / width;
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000812 retval = static_cast<int>(height * scale);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000813 }
814 return retval;
815}
816
epoger@google.com25d961c2012-02-02 20:50:36 +0000817static void print_table_header (SkFILEWStream* stream,
818 const int matchCount,
819 const int colorThreshold,
820 const RecordArray& differences,
821 const SkString &baseDir,
epoger@google.coma2b793c2012-05-15 14:58:53 +0000822 const SkString &comparisonDir,
823 bool doOutputDate=false) {
epoger@google.com25d961c2012-02-02 20:50:36 +0000824 stream->writeText("<table>\n");
825 stream->writeText("<tr><th>");
epoger@google.coma2b793c2012-05-15 14:58:53 +0000826 if (doOutputDate) {
827 SkTime::DateTime dt;
828 SkTime::GetDateTime(&dt);
829 stream->writeText("SkDiff run at ");
830 stream->writeDecAsText(dt.fHour);
831 stream->writeText(":");
832 if (dt.fMinute < 10) {
833 stream->writeText("0");
834 }
835 stream->writeDecAsText(dt.fMinute);
836 stream->writeText(":");
837 if (dt.fSecond < 10) {
838 stream->writeText("0");
839 }
840 stream->writeDecAsText(dt.fSecond);
841 stream->writeText("<br>");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000842 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000843 stream->writeDecAsText(matchCount);
844 stream->writeText(" of ");
845 stream->writeDecAsText(differences.count());
846 stream->writeText(" images matched ");
847 if (colorThreshold == 0) {
848 stream->writeText("exactly");
849 } else {
850 stream->writeText("within ");
851 stream->writeDecAsText(colorThreshold);
852 stream->writeText(" color units per component");
853 }
854 stream->writeText(".<br>");
epoger@google.com25d961c2012-02-02 20:50:36 +0000855 stream->writeText("</th>\n<th>");
856 stream->writeText("every different pixel shown in white");
857 stream->writeText("</th>\n<th>");
858 stream->writeText("color difference at each pixel");
epoger@google.com46a45962012-07-12 18:16:02 +0000859 stream->writeText("</th>\n<th>baseDir: ");
epoger@google.com25d961c2012-02-02 20:50:36 +0000860 stream->writeText(baseDir.c_str());
epoger@google.com46a45962012-07-12 18:16:02 +0000861 stream->writeText("</th>\n<th>comparisonDir: ");
epoger@google.com25d961c2012-02-02 20:50:36 +0000862 stream->writeText(comparisonDir.c_str());
863 stream->writeText("</th>\n");
864 stream->writeText("</tr>\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000865}
866
867static void print_pixel_count (SkFILEWStream* stream,
868 const DiffRecord& diff) {
869 stream->writeText("<br>(");
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000870 stream->writeDecAsText(static_cast<int>(diff.fFractionDifference *
871 diff.fBaseWidth *
872 diff.fBaseHeight));
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000873 stream->writeText(" pixels)");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000874/*
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000875 stream->writeDecAsText(diff.fWeightedFraction *
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000876 diff.fBaseWidth *
877 diff.fBaseHeight);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000878 stream->writeText(" weighted pixels)");
879*/
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000880}
881
882static void print_label_cell (SkFILEWStream* stream,
883 const DiffRecord& diff) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000884 char metricBuf [20];
885
886 stream->writeText("<td><b>");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000887 stream->writeText(diff.fFilename.c_str());
epoger@google.com46256ea2012-05-22 13:45:35 +0000888 stream->writeText("</b><br>");
epoger@google.com292aff62012-05-16 14:57:28 +0000889 switch (diff.fResult) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000890 case kEqualBits:
891 SkDEBUGFAIL("should not encounter DiffRecord with kEqualBits here");
892 return;
893 case kEqualPixels:
894 SkDEBUGFAIL("should not encounter DiffRecord with kEqualPixels here");
epoger@google.com5fd53852012-03-22 18:20:06 +0000895 return;
epoger@google.com292aff62012-05-16 14:57:28 +0000896 case kDifferentSizes:
epoger@google.com46256ea2012-05-22 13:45:35 +0000897 stream->writeText("Image sizes differ</td>");
898 return;
899 case kDifferentPixels:
900 sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
901 stream->writeText(metricBuf);
902 stream->writeText(" of pixels differ");
903 stream->writeText("\n (");
904 sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
905 stream->writeText(metricBuf);
906 stream->writeText(" weighted)");
907 // Write the actual number of pixels that differ if it's < 1%
908 if (diff.fFractionDifference < 0.01) {
909 print_pixel_count(stream, diff);
910 }
911 stream->writeText("<br>Average color mismatch ");
912 stream->writeDecAsText(static_cast<int>(MAX3(diff.fAverageMismatchR,
913 diff.fAverageMismatchG,
914 diff.fAverageMismatchB)));
915 stream->writeText("<br>Max color mismatch ");
916 stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
917 diff.fMaxMismatchG,
918 diff.fMaxMismatchB));
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000919 stream->writeText("</td>");
epoger@google.com46256ea2012-05-22 13:45:35 +0000920 break;
921 case kDifferentOther:
922 stream->writeText("Files differ; unable to parse one or both files</td>");
923 return;
924 case kBaseMissing:
925 stream->writeText("Missing from baseDir</td>");
926 return;
927 case kComparisonMissing:
928 stream->writeText("Missing from comparisonDir</td>");
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000929 return;
epoger@google.com292aff62012-05-16 14:57:28 +0000930 default:
epoger@google.com46256ea2012-05-22 13:45:35 +0000931 SkDEBUGFAIL("encountered DiffRecord with unknown result type");
932 return;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000933 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000934}
935
936static void print_image_cell (SkFILEWStream* stream,
tomhudson@google.com4e305982011-07-13 17:42:46 +0000937 const SkString& path,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000938 int height) {
939 stream->writeText("<td><a href=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000940 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000941 stream->writeText("\"><img src=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000942 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000943 stream->writeText("\" height=\"");
944 stream->writeDecAsText(height);
945 stream->writeText("px\"></a></td>");
946}
947
caryclark@google.com3dd45912012-06-06 12:11:10 +0000948#if 0 // UNUSED
epoger@google.com01f78702012-04-12 16:32:04 +0000949static void print_text_cell (SkFILEWStream* stream, const char* text) {
950 stream->writeText("<td align=center>");
951 if (NULL != text) {
952 stream->writeText(text);
953 }
954 stream->writeText("</td>");
955}
caryclark@google.com3dd45912012-06-06 12:11:10 +0000956#endif
epoger@google.com01f78702012-04-12 16:32:04 +0000957
epoger@google.com5fd53852012-03-22 18:20:06 +0000958static void print_diff_with_missing_file(SkFILEWStream* stream,
959 DiffRecord& diff,
960 const SkString& relativePath) {
961 stream->writeText("<tr>\n");
962 print_label_cell(stream, diff);
963 stream->writeText("<td>N/A</td>");
964 stream->writeText("<td>N/A</td>");
epoger@google.com292aff62012-05-16 14:57:28 +0000965 if (kBaseMissing != diff.fResult) {
epoger@google.com5fd53852012-03-22 18:20:06 +0000966 int h, w;
967 if (!get_bitmap_height_width(diff.fBasePath, &h, &w)) {
968 stream->writeText("<td>N/A</td>");
969 } else {
970 int height = compute_image_height(h, w);
971 if (!diff.fBasePath.startsWith(PATH_DIV_STR)) {
972 diff.fBasePath.prepend(relativePath);
973 }
974 print_image_cell(stream, diff.fBasePath, height);
975 }
976 } else {
977 stream->writeText("<td>N/A</td>");
978 }
epoger@google.com292aff62012-05-16 14:57:28 +0000979 if (kComparisonMissing != diff.fResult) {
epoger@google.com5fd53852012-03-22 18:20:06 +0000980 int h, w;
981 if (!get_bitmap_height_width(diff.fComparisonPath, &h, &w)) {
982 stream->writeText("<td>N/A</td>");
983 } else {
984 int height = compute_image_height(h, w);
985 if (!diff.fComparisonPath.startsWith(PATH_DIV_STR)) {
986 diff.fComparisonPath.prepend(relativePath);
987 }
988 print_image_cell(stream, diff.fComparisonPath, height);
989 }
990 } else {
991 stream->writeText("<td>N/A</td>");
992 }
993 stream->writeText("</tr>\n");
994 stream->flush();
995}
996
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000997static void print_diff_page (const int matchCount,
998 const int colorThreshold,
999 const RecordArray& differences,
1000 const SkString& baseDir,
1001 const SkString& comparisonDir,
1002 const SkString& outputDir) {
1003
epoger@google.coma5f406e2012-05-01 13:26:16 +00001004 SkASSERT(!baseDir.isEmpty());
1005 SkASSERT(!comparisonDir.isEmpty());
1006 SkASSERT(!outputDir.isEmpty());
1007
tomhudson@google.com5b325292011-05-24 19:41:13 +00001008 SkString outputPath (outputDir);
1009 outputPath.append("index.html");
1010 //SkFILEWStream outputStream ("index.html");
1011 SkFILEWStream outputStream (outputPath.c_str());
1012
tomhudson@google.com4e305982011-07-13 17:42:46 +00001013 // Need to convert paths from relative-to-cwd to relative-to-outputDir
tomhudson@google.com5b325292011-05-24 19:41:13 +00001014 // FIXME this doesn't work if there are '..' inside the outputDir
1015 unsigned int ui;
1016 SkString relativePath;
1017 for (ui = 0; ui < outputDir.size(); ui++) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001018 if (outputDir[ui] == PATH_DIV_CHAR) {
1019 relativePath.append(".." PATH_DIV_STR);
tomhudson@google.com5b325292011-05-24 19:41:13 +00001020 }
1021 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001022
1023 outputStream.writeText("<html>\n<body>\n");
epoger@google.com25d961c2012-02-02 20:50:36 +00001024 print_table_header(&outputStream, matchCount, colorThreshold, differences,
1025 baseDir, comparisonDir);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001026 int i;
1027 for (i = 0; i < differences.count(); i++) {
1028 DiffRecord* diff = differences[i];
epoger@google.com5fd53852012-03-22 18:20:06 +00001029
epoger@google.com292aff62012-05-16 14:57:28 +00001030 switch (diff->fResult) {
epoger@google.com46256ea2012-05-22 13:45:35 +00001031 // Cases in which there is no diff to report.
1032 case kEqualBits:
epoger@google.com292aff62012-05-16 14:57:28 +00001033 case kEqualPixels:
1034 continue;
epoger@google.com46256ea2012-05-22 13:45:35 +00001035 // Cases in which we want a detailed pixel diff.
1036 case kDifferentPixels:
1037 break;
1038 // Cases in which the files differed, but we can't display the diff.
1039 case kDifferentSizes:
1040 case kDifferentOther:
epoger@google.com292aff62012-05-16 14:57:28 +00001041 case kBaseMissing:
epoger@google.com292aff62012-05-16 14:57:28 +00001042 case kComparisonMissing:
epoger@google.com5fd53852012-03-22 18:20:06 +00001043 print_diff_with_missing_file(&outputStream, *diff, relativePath);
1044 continue;
epoger@google.com292aff62012-05-16 14:57:28 +00001045 default:
epoger@google.com46256ea2012-05-22 13:45:35 +00001046 SkDEBUGFAIL("encountered DiffRecord with unknown result type");
1047 continue;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001048 }
epoger@google.com5fd53852012-03-22 18:20:06 +00001049
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001050 if (!diff->fBasePath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +00001051 diff->fBasePath.prepend(relativePath);
1052 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001053 if (!diff->fComparisonPath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +00001054 diff->fComparisonPath.prepend(relativePath);
1055 }
epoger@google.com5fd53852012-03-22 18:20:06 +00001056
tomhudson@google.com9b540ce2011-08-02 14:10:04 +00001057 int height = compute_image_height(diff->fBaseHeight, diff->fBaseWidth);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001058 outputStream.writeText("<tr>\n");
1059 print_label_cell(&outputStream, *diff);
epoger@google.com46256ea2012-05-22 13:45:35 +00001060 print_image_cell(&outputStream,
1061 filename_to_white_filename(diff->fFilename), height);
1062 print_image_cell(&outputStream,
1063 filename_to_diff_filename(diff->fFilename), height);
epoger@google.com25d961c2012-02-02 20:50:36 +00001064 print_image_cell(&outputStream, diff->fBasePath, height);
tomhudson@google.com4e305982011-07-13 17:42:46 +00001065 print_image_cell(&outputStream, diff->fComparisonPath, height);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001066 outputStream.writeText("</tr>\n");
1067 outputStream.flush();
1068 }
1069 outputStream.writeText("</table>\n");
1070 outputStream.writeText("</body>\n</html>\n");
1071 outputStream.flush();
1072}
1073
1074static void usage (char * argv0) {
1075 SkDebugf("Skia baseline image diff tool\n");
epoger@google.coma5f406e2012-05-01 13:26:16 +00001076 SkDebugf("\n"
1077"Usage: \n"
1078" %s <baseDir> <comparisonDir> [outputDir] \n"
epoger@google.coma611c3e2012-05-18 20:10:06 +00001079, argv0, argv0);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001080 SkDebugf(
epoger@google.com46a45962012-07-12 18:16:02 +00001081"\nArguments:"
1082"\n --help: display this info"
1083"\n --failonmismatches: exit with nonzero return code (number of mismatching"
1084"\n image pairs) if any pairs differ by more than threshold;"
1085"\n otherwise, only return nonzero if the tool itself fails"
1086"\n --listfilenames: list all filenames for each result type in stdout"
1087"\n --match: compare files whose filenames contain this substring; if"
1088"\n unspecified, compare ALL files."
1089"\n this flag may be repeated to add more matching substrings."
1090"\n --nodiffs: don't write out image diffs or index.html, just generate"
1091"\n report on stdout"
1092"\n --nomatch: regardless of --match, DO NOT compare files whose filenames"
1093"\n contain this substring."
1094"\n this flag may be repeated to add more forbidden substrings."
1095"\n --noprintdirs: do not print the directories used."
1096"\n --sortbymaxmismatch: sort by worst color channel mismatch;"
1097"\n break ties with -sortbymismatch"
1098"\n --sortbymismatch: sort by average color channel mismatch"
1099"\n --threshold <n>: only report differences > n (per color channel) [default 0]"
1100"\n --weighted: sort by # pixels different weighted by color difference"
1101"\n"
1102"\n baseDir: directory to read baseline images from."
1103"\n comparisonDir: directory to read comparison images from"
1104"\n outputDir: directory to write difference images and index.html to;"
1105"\n defaults to comparisonDir"
1106"\n"
1107"\nIf no sort is specified, it will sort by fraction of pixels mismatching."
1108"\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001109}
1110
epoger@google.com70044cc2012-07-12 18:37:55 +00001111const int kNoError = 0;
1112const int kGenericError = -1;
epoger@google.com46a45962012-07-12 18:16:02 +00001113
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001114int main (int argc, char ** argv) {
1115 DiffMetricProc diffProc = compute_diff_pmcolor;
epoger@google.com28060e72012-06-28 16:47:34 +00001116 int (*sortProc)(const void*, const void*) = compare<CompareDiffMetrics>;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001117
1118 // Maximum error tolerated in any one color channel in any one pixel before
1119 // a difference is reported.
1120 int colorThreshold = 0;
1121 SkString baseDir;
1122 SkString comparisonDir;
1123 SkString outputDir;
epoger@google.coma5f406e2012-05-01 13:26:16 +00001124 StringArray matchSubstrings;
1125 StringArray nomatchSubstrings;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001126
epoger@google.com46a45962012-07-12 18:16:02 +00001127 bool failOnMismatches = false;
epoger@google.coma5f406e2012-05-01 13:26:16 +00001128 bool generateDiffs = true;
epoger@google.com46a45962012-07-12 18:16:02 +00001129 bool listFilenames = false;
keyar@chromium.orga6318192012-07-09 21:01:50 +00001130 bool printDirs = true;
tomhudson@google.com7d042802011-07-14 13:15:55 +00001131
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001132 RecordArray differences;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001133 DiffSummary summary;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001134
epoger@google.coma5f406e2012-05-01 13:26:16 +00001135 int i;
1136 int numUnflaggedArguments = 0;
1137 for (i = 1; i < argc; i++) {
epoger@google.com46a45962012-07-12 18:16:02 +00001138 if (!strcmp(argv[i], "--failonmismatches")) {
1139 failOnMismatches = true;
epoger@google.coma5f406e2012-05-01 13:26:16 +00001140 continue;
1141 }
epoger@google.com46a45962012-07-12 18:16:02 +00001142 if (!strcmp(argv[i], "--help")) {
1143 usage(argv[0]);
epoger@google.com70044cc2012-07-12 18:37:55 +00001144 return kNoError;
epoger@google.com46a45962012-07-12 18:16:02 +00001145 }
1146 if (!strcmp(argv[i], "--listfilenames")) {
1147 listFilenames = true;
epoger@google.coma5f406e2012-05-01 13:26:16 +00001148 continue;
1149 }
1150 if (!strcmp(argv[i], "--match")) {
1151 matchSubstrings.push(new SkString(argv[++i]));
1152 continue;
1153 }
epoger@google.com46a45962012-07-12 18:16:02 +00001154 if (!strcmp(argv[i], "--nodiffs")) {
1155 generateDiffs = false;
1156 continue;
1157 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001158 if (!strcmp(argv[i], "--nomatch")) {
1159 nomatchSubstrings.push(new SkString(argv[++i]));
1160 continue;
1161 }
epoger@google.com46a45962012-07-12 18:16:02 +00001162 if (!strcmp(argv[i], "--noprintdirs")) {
1163 printDirs = false;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001164 continue;
1165 }
tomhudson@google.com7d042802011-07-14 13:15:55 +00001166 if (!strcmp(argv[i], "--sortbymaxmismatch")) {
epoger@google.com28060e72012-06-28 16:47:34 +00001167 sortProc = compare<CompareDiffMaxMismatches>;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001168 continue;
1169 }
epoger@google.com46a45962012-07-12 18:16:02 +00001170 if (!strcmp(argv[i], "--sortbymismatch")) {
1171 sortProc = compare<CompareDiffMeanMismatches>;
tomhudson@google.com5b325292011-05-24 19:41:13 +00001172 continue;
1173 }
epoger@google.com46a45962012-07-12 18:16:02 +00001174 if (!strcmp(argv[i], "--threshold")) {
1175 colorThreshold = atoi(argv[++i]);
1176 continue;
1177 }
1178 if (!strcmp(argv[i], "--weighted")) {
1179 sortProc = compare<CompareDiffWeighted>;
keyar@chromium.orga6318192012-07-09 21:01:50 +00001180 continue;
1181 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001182 if (argv[i][0] != '-') {
epoger@google.coma5f406e2012-05-01 13:26:16 +00001183 switch (numUnflaggedArguments++) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001184 case 0:
1185 baseDir.set(argv[i]);
1186 continue;
1187 case 1:
1188 comparisonDir.set(argv[i]);
1189 continue;
1190 case 2:
1191 outputDir.set(argv[i]);
1192 continue;
1193 default:
epoger@google.coma5f406e2012-05-01 13:26:16 +00001194 SkDebugf("extra unflagged argument <%s>\n", argv[i]);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001195 usage(argv[0]);
epoger@google.com70044cc2012-07-12 18:37:55 +00001196 return kGenericError;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001197 }
1198 }
1199
1200 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
1201 usage(argv[0]);
epoger@google.com70044cc2012-07-12 18:37:55 +00001202 return kGenericError;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001203 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001204
epoger@google.coma5f406e2012-05-01 13:26:16 +00001205 if (numUnflaggedArguments == 2) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001206 outputDir = comparisonDir;
epoger@google.coma5f406e2012-05-01 13:26:16 +00001207 } else if (numUnflaggedArguments != 3) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001208 usage(argv[0]);
epoger@google.com70044cc2012-07-12 18:37:55 +00001209 return kGenericError;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001210 }
1211
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001212 if (!baseDir.endsWith(PATH_DIV_STR)) {
1213 baseDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001214 }
keyar@chromium.orga6318192012-07-09 21:01:50 +00001215 if (printDirs) {
1216 printf("baseDir is [%s]\n", baseDir.c_str());
1217 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001218
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001219 if (!comparisonDir.endsWith(PATH_DIV_STR)) {
1220 comparisonDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001221 }
keyar@chromium.orga6318192012-07-09 21:01:50 +00001222 if (printDirs) {
1223 printf("comparisonDir is [%s]\n", comparisonDir.c_str());
1224 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001225
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001226 if (!outputDir.endsWith(PATH_DIV_STR)) {
1227 outputDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001228 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001229 if (generateDiffs) {
keyar@chromium.orga6318192012-07-09 21:01:50 +00001230 if (printDirs) {
1231 printf("writing diffs to outputDir is [%s]\n", outputDir.c_str());
1232 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001233 } else {
keyar@chromium.orga6318192012-07-09 21:01:50 +00001234 if (printDirs) {
1235 printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str());
1236 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001237 outputDir.set("");
1238 }
1239
epoger@google.comda4af242012-06-25 18:45:50 +00001240 // If no matchSubstrings were specified, match ALL strings
1241 // (except for whatever nomatchSubstrings were specified, if any).
epoger@google.coma5f406e2012-05-01 13:26:16 +00001242 if (matchSubstrings.isEmpty()) {
1243 matchSubstrings.push(new SkString(""));
1244 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001245
epoger@google.coma611c3e2012-05-18 20:10:06 +00001246 create_diff_images(diffProc, colorThreshold, &differences,
1247 baseDir, comparisonDir, outputDir,
1248 matchSubstrings, nomatchSubstrings, &summary);
epoger@google.com46a45962012-07-12 18:16:02 +00001249 summary.print(listFilenames);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001250
1251 if (differences.count()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +00001252 qsort(differences.begin(), differences.count(),
1253 sizeof(DiffRecord*), sortProc);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001254 }
epoger@google.com66008522012-05-16 17:40:57 +00001255
epoger@google.coma5f406e2012-05-01 13:26:16 +00001256 if (generateDiffs) {
1257 print_diff_page(summary.fNumMatches, colorThreshold, differences,
1258 baseDir, comparisonDir, outputDir);
1259 }
epoger@google.com76222c02012-05-31 15:12:09 +00001260
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001261 for (i = 0; i < differences.count(); i++) {
1262 delete differences[i];
1263 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001264 matchSubstrings.deleteAll();
1265 nomatchSubstrings.deleteAll();
epoger@google.combe6188d2012-05-31 15:13:45 +00001266
epoger@google.com46a45962012-07-12 18:16:02 +00001267 if (failOnMismatches) {
1268 return summary.fNumMismatches;
1269 } else {
epoger@google.com70044cc2012-07-12 18:37:55 +00001270 return kNoError;
epoger@google.com46a45962012-07-12 18:16:02 +00001271 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001272}