blob: f20475aedb0565cd83452090446ee76ed53f3e3e [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.comdfbf24e2012-07-13 21:22:02 +000057// Returns the Result with this name.
58// If there is no Result with this name, returns kNumResultTypes.
59// TODO: Is there a better return value for the fall-through case?
60Result getResultByName(const char *name) {
61 if (0 == strcmp("EqualBits", name)) {
62 return kEqualBits;
63 }
64 if (0 == strcmp("EqualPixels", name)) {
65 return kEqualPixels;
66 }
67 if (0 == strcmp("DifferentPixels", name)) {
68 return kDifferentPixels;
69 }
70 if (0 == strcmp("DifferentSizes", name)) {
71 return kDifferentSizes;
72 }
73 if (0 == strcmp("DifferentOther", name)) {
74 return kDifferentOther;
75 }
76 if (0 == strcmp("ComparisonMissing", name)) {
77 return kComparisonMissing;
78 }
79 if (0 == strcmp("BaseMissing", name)) {
80 return kBaseMissing;
81 }
82 if (0 == strcmp("Unknown", name)) {
83 return kUnknown;
84 }
85 return kNumResultTypes;
86}
87
epoger@google.com46a45962012-07-12 18:16:02 +000088// Returns a text description of the given Result type.
89const char *getResultDescription(Result result) {
90 switch (result) {
91 case kEqualBits:
92 return "contain exactly the same bits";
93 case kEqualPixels:
94 return "contain the same pixel values, but not the same bits";
95 case kDifferentPixels:
96 return "have identical dimensions but some differing pixels";
97 case kDifferentSizes:
98 return "have differing dimensions";
99 case kDifferentOther:
100 return "contain different bits and are not parsable images";
101 case kBaseMissing:
102 return "missing from baseDir";
103 case kComparisonMissing:
104 return "missing from comparisonDir";
105 case kUnknown:
106 return "not compared yet";
107 default:
108 return NULL;
109 }
110}
111
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000112struct DiffRecord {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000113 DiffRecord (const SkString filename,
114 const SkString basePath,
epoger@google.com5fd53852012-03-22 18:20:06 +0000115 const SkString comparisonPath,
epoger@google.com292aff62012-05-16 14:57:28 +0000116 const Result result = kUnknown)
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000117 : fFilename (filename)
tomhudson@google.com4e305982011-07-13 17:42:46 +0000118 , fBasePath (basePath)
119 , fComparisonPath (comparisonPath)
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000120 , fBaseBitmap (new SkBitmap ())
121 , fComparisonBitmap (new SkBitmap ())
122 , fDifferenceBitmap (new SkBitmap ())
epoger@google.com25d961c2012-02-02 20:50:36 +0000123 , fWhiteBitmap (new SkBitmap ())
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000124 , fBaseHeight (0)
125 , fBaseWidth (0)
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000126 , fFractionDifference (0)
127 , fWeightedFraction (0)
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000128 , fAverageMismatchR (0)
129 , fAverageMismatchG (0)
130 , fAverageMismatchB (0)
131 , fMaxMismatchR (0)
132 , fMaxMismatchG (0)
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000133 , fMaxMismatchB (0)
epoger@google.com292aff62012-05-16 14:57:28 +0000134 , fResult(result) {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000135 };
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000136
137 SkString fFilename;
tomhudson@google.com4e305982011-07-13 17:42:46 +0000138 SkString fBasePath;
139 SkString fComparisonPath;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000140
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000141 SkBitmap* fBaseBitmap;
142 SkBitmap* fComparisonBitmap;
143 SkBitmap* fDifferenceBitmap;
epoger@google.com25d961c2012-02-02 20:50:36 +0000144 SkBitmap* fWhiteBitmap;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000145
146 int fBaseHeight;
147 int fBaseWidth;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000148
149 /// Arbitrary floating-point metric to be used to sort images from most
150 /// to least different from baseline; values of 0 will be omitted from the
151 /// summary webpage.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000152 float fFractionDifference;
153 float fWeightedFraction;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000154
155 float fAverageMismatchR;
156 float fAverageMismatchG;
157 float fAverageMismatchB;
158
159 uint32_t fMaxMismatchR;
160 uint32_t fMaxMismatchG;
161 uint32_t fMaxMismatchB;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000162
epoger@google.com292aff62012-05-16 14:57:28 +0000163 /// Which category of diff result.
164 Result fResult;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000165};
166
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000167#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
168#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
169
epoger@google.com25d961c2012-02-02 20:50:36 +0000170const SkPMColor PMCOLOR_WHITE = SkPreMultiplyColor(SK_ColorWHITE);
171const SkPMColor PMCOLOR_BLACK = SkPreMultiplyColor(SK_ColorBLACK);
172
epoger@google.coma5f406e2012-05-01 13:26:16 +0000173typedef SkTDArray<SkString*> StringArray;
174typedef StringArray FileArray;
epoger@google.com5fd53852012-03-22 18:20:06 +0000175
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000176struct DiffSummary {
177 DiffSummary ()
178 : fNumMatches (0)
179 , fNumMismatches (0)
180 , fMaxMismatchV (0)
181 , fMaxMismatchPercent (0) { };
182
epoger@google.com5fd53852012-03-22 18:20:06 +0000183 ~DiffSummary() {
epoger@google.com76222c02012-05-31 15:12:09 +0000184 for (int i = 0; i < kNumResultTypes; i++) {
185 fResultsOfType[i].deleteAll();
186 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000187 }
188
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000189 uint32_t fNumMatches;
190 uint32_t fNumMismatches;
191 uint32_t fMaxMismatchV;
192 float fMaxMismatchPercent;
193
epoger@google.com76222c02012-05-31 15:12:09 +0000194 FileArray fResultsOfType[kNumResultTypes];
195
epoger@google.com46a45962012-07-12 18:16:02 +0000196 // Print a line about the contents of this FileArray to stdout; if the FileArray is empty,
197 // print nothing.
198 void printContents(const FileArray& fileArray, const char* headerText, bool listFilenames) {
epoger@google.com76222c02012-05-31 15:12:09 +0000199 int n = fileArray.count();
200 if (n > 0) {
epoger@google.com46a45962012-07-12 18:16:02 +0000201 printf(" %d file pairs %s", n, headerText);
202 if (listFilenames) {
203 printf(": ");
204 for (int i = 0; i < n; ++i) {
205 printf("%s ", fileArray[i]->c_str());
206 }
epoger@google.com76222c02012-05-31 15:12:09 +0000207 }
epoger@google.com46a45962012-07-12 18:16:02 +0000208 printf("\n");
epoger@google.com76222c02012-05-31 15:12:09 +0000209 }
210 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000211
epoger@google.com46a45962012-07-12 18:16:02 +0000212 void print(bool listFilenames) {
213 printf("compared %d file pairs:\n", fNumMatches + fNumMismatches);
214 for (int resultInt = 0; resultInt < kNumResultTypes; resultInt++) {
215 Result result = static_cast<Result>(resultInt);
216 printContents(fResultsOfType[result], getResultDescription(result), listFilenames);
217 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000218 if (fNumMismatches > 0) {
219 printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
epoger@google.com46a45962012-07-12 18:16:02 +0000220 printf("Largest area mismatch was %.2f%% of pixels\n",fMaxMismatchPercent);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000221 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000222 }
223
224 void add (DiffRecord* drp) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000225 uint32_t mismatchValue;
epoger@google.com292aff62012-05-16 14:57:28 +0000226
epoger@google.com76222c02012-05-31 15:12:09 +0000227 fResultsOfType[drp->fResult].push(new SkString(drp->fFilename));
epoger@google.com292aff62012-05-16 14:57:28 +0000228 switch (drp->fResult) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000229 case kEqualBits:
230 fNumMatches++;
231 break;
epoger@google.com292aff62012-05-16 14:57:28 +0000232 case kEqualPixels:
233 fNumMatches++;
234 break;
epoger@google.com46256ea2012-05-22 13:45:35 +0000235 case kDifferentSizes:
epoger@google.com5fd53852012-03-22 18:20:06 +0000236 fNumMismatches++;
epoger@google.com292aff62012-05-16 14:57:28 +0000237 break;
epoger@google.com46256ea2012-05-22 13:45:35 +0000238 case kDifferentPixels:
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000239 fNumMismatches++;
240 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
241 fMaxMismatchPercent = drp->fFractionDifference * 100;
242 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000243 mismatchValue = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
244 drp->fMaxMismatchB);
245 if (mismatchValue > fMaxMismatchV) {
246 fMaxMismatchV = mismatchValue;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000247 }
epoger@google.com292aff62012-05-16 14:57:28 +0000248 break;
epoger@google.com46256ea2012-05-22 13:45:35 +0000249 case kDifferentOther:
250 fNumMismatches++;
epoger@google.com46256ea2012-05-22 13:45:35 +0000251 break;
252 case kBaseMissing:
253 fNumMismatches++;
epoger@google.com46256ea2012-05-22 13:45:35 +0000254 break;
255 case kComparisonMissing:
256 fNumMismatches++;
epoger@google.com46256ea2012-05-22 13:45:35 +0000257 break;
258 case kUnknown:
259 SkDEBUGFAIL("adding uncategorized DiffRecord");
260 break;
261 default:
262 SkDEBUGFAIL("adding DiffRecord with unhandled fResult value");
263 break;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000264 }
265 }
266};
267
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000268typedef SkTDArray<DiffRecord*> RecordArray;
269
epoger@google.com28060e72012-06-28 16:47:34 +0000270/// A wrapper for any sortProc (comparison routine) which applies a first-order
271/// sort beforehand, and a tiebreaker if the sortProc returns 0.
272template<typename T>
273static int compare(const void* untyped_lhs, const void* untyped_rhs) {
274 const DiffRecord* lhs = *reinterpret_cast<DiffRecord* const*>(untyped_lhs);
275 const DiffRecord* rhs = *reinterpret_cast<DiffRecord* const*>(untyped_rhs);
276
277 // First-order sort... these comparisons should be applied before comparing
278 // pixel values, no matter what.
279 if (lhs->fResult != rhs->fResult) {
280 return (lhs->fResult < rhs->fResult) ? 1 : -1;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000281 }
epoger@google.com28060e72012-06-28 16:47:34 +0000282
283 // Passed first-order sort, so call the pixel comparison routine.
284 int result = T::comparePixels(lhs, rhs);
285 if (result != 0) {
286 return result;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000287 }
epoger@google.com28060e72012-06-28 16:47:34 +0000288
289 // Tiebreaker... if we got to this point, we don't really care
290 // which order they are sorted in, but let's at least be consistent.
291 return strcmp(lhs->fFilename.c_str(), rhs->fFilename.c_str());
tomhudson@google.com5b325292011-05-24 19:41:13 +0000292}
293
epoger@google.com28060e72012-06-28 16:47:34 +0000294/// Comparison routine for qsort; sorts by fFractionDifference
295/// from largest to smallest.
296class CompareDiffMetrics {
297public:
298 static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
299 if (lhs->fFractionDifference < rhs->fFractionDifference) {
300 return 1;
301 }
302 if (rhs->fFractionDifference < lhs->fFractionDifference) {
303 return -1;
304 }
305 return 0;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000306 }
epoger@google.com28060e72012-06-28 16:47:34 +0000307};
308
309class CompareDiffWeighted {
310public:
311 static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
312 if (lhs->fWeightedFraction < rhs->fWeightedFraction) {
313 return 1;
314 }
315 if (lhs->fWeightedFraction > rhs->fWeightedFraction) {
316 return -1;
317 }
318 return 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000319 }
epoger@google.com28060e72012-06-28 16:47:34 +0000320};
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000321
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000322/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
323/// from largest to smallest.
epoger@google.com28060e72012-06-28 16:47:34 +0000324class CompareDiffMeanMismatches {
325public:
326 static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
327 float leftValue = MAX3(lhs->fAverageMismatchR,
328 lhs->fAverageMismatchG,
329 lhs->fAverageMismatchB);
330 float rightValue = MAX3(rhs->fAverageMismatchR,
331 rhs->fAverageMismatchG,
332 rhs->fAverageMismatchB);
333 if (leftValue < rightValue) {
334 return 1;
335 }
336 if (rightValue < leftValue) {
337 return -1;
338 }
339 return 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000340 }
epoger@google.com28060e72012-06-28 16:47:34 +0000341};
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000342
343/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB})
344/// from largest to smallest.
epoger@google.com28060e72012-06-28 16:47:34 +0000345class CompareDiffMaxMismatches {
346public:
347 static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
348 uint32_t leftValue = MAX3(lhs->fMaxMismatchR,
349 lhs->fMaxMismatchG,
350 lhs->fMaxMismatchB);
351 uint32_t rightValue = MAX3(rhs->fMaxMismatchR,
352 rhs->fMaxMismatchG,
353 rhs->fMaxMismatchB);
354 if (leftValue < rightValue) {
355 return 1;
356 }
357 if (rightValue < leftValue) {
358 return -1;
359 }
360
361 return CompareDiffMeanMismatches::comparePixels(lhs, rhs);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000362 }
epoger@google.com28060e72012-06-28 16:47:34 +0000363};
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000364
365
366
367/// Parameterized routine to compute the color of a pixel in a difference image.
368typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
369
caryclark@google.com3dd45912012-06-06 12:11:10 +0000370#if 0 // UNUSED
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000371static void expand_and_copy (int width, int height, SkBitmap** dest) {
372 SkBitmap* temp = new SkBitmap ();
373 temp->reset();
374 temp->setConfig((*dest)->config(), width, height);
375 temp->allocPixels();
376 (*dest)->copyPixelsTo(temp->getPixels(), temp->getSize(),
377 temp->rowBytes());
378 *dest = temp;
379}
caryclark@google.com3dd45912012-06-06 12:11:10 +0000380#endif
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000381
epoger@google.com46256ea2012-05-22 13:45:35 +0000382/// Returns true if the two buffers passed in are both non-NULL, and include
383/// exactly the same byte values (and identical lengths).
384static bool are_buffers_equal(SkData* skdata1, SkData* skdata2) {
385 if ((NULL == skdata1) || (NULL == skdata2)) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000386 return false;
387 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000388 if (skdata1->size() != skdata2->size()) {
389 return false;
390 }
391 return (0 == memcmp(skdata1->data(), skdata2->data(), skdata1->size()));
392}
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000393
epoger@google.com46256ea2012-05-22 13:45:35 +0000394/// Reads the file at the given path and returns its complete contents as an
395/// SkData object (or returns NULL on error).
396static SkData* read_file(const char* file_path) {
397 SkFILEStream fileStream(file_path);
398 if (!fileStream.isValid()) {
399 SkDebugf("WARNING: could not open file <%s> for reading\n", file_path);
400 return NULL;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000401 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000402 size_t bytesInFile = fileStream.getLength();
403 size_t bytesLeftToRead = bytesInFile;
404
405 void* bufferStart = sk_malloc_throw(bytesInFile);
406 char* bufferPointer = (char*)bufferStart;
407 while (bytesLeftToRead > 0) {
408 size_t bytesReadThisTime = fileStream.read(
409 bufferPointer, bytesLeftToRead);
410 if (0 == bytesReadThisTime) {
411 SkDebugf("WARNING: error reading from <%s>\n", file_path);
412 sk_free(bufferStart);
413 return NULL;
414 }
415 bytesLeftToRead -= bytesReadThisTime;
416 bufferPointer += bytesReadThisTime;
417 }
418 return SkData::NewFromMalloc(bufferStart, bytesInFile);
419}
420
421/// Decodes binary contents of baseFile and comparisonFile into
422/// diffRecord->fBaseBitmap and diffRecord->fComparisonBitmap.
423/// Returns true if that succeeds.
424static bool get_bitmaps (SkData* baseFileContents,
425 SkData* comparisonFileContents,
426 DiffRecord* diffRecord) {
427 SkMemoryStream compareStream(comparisonFileContents->data(),
428 comparisonFileContents->size());
429 SkMemoryStream baseStream(baseFileContents->data(),
430 baseFileContents->size());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000431
432 SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
433 if (NULL == codec) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000434 SkDebugf("ERROR: no codec found for basePath <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000435 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000436 return false;
437 }
438
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000439 // In debug, the DLL will automatically be unloaded when this is deleted,
440 // but that shouldn't be a problem in release mode.
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000441 SkAutoTDelete<SkImageDecoder> ad(codec);
442
443 baseStream.rewind();
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000444 if (!codec->decode(&baseStream, diffRecord->fBaseBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000445 SkBitmap::kARGB_8888_Config,
446 SkImageDecoder::kDecodePixels_Mode)) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000447 SkDebugf("ERROR: codec failed for basePath <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000448 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000449 return false;
450 }
451
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000452 diffRecord->fBaseWidth = diffRecord->fBaseBitmap->width();
453 diffRecord->fBaseHeight = diffRecord->fBaseBitmap->height();
454
455 if (!codec->decode(&compareStream, diffRecord->fComparisonBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000456 SkBitmap::kARGB_8888_Config,
457 SkImageDecoder::kDecodePixels_Mode)) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000458 SkDebugf("ERROR: codec failed for comparisonPath <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000459 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000460 return false;
461 }
462
463 return true;
464}
465
epoger@google.com5fd53852012-03-22 18:20:06 +0000466static bool get_bitmap_height_width(const SkString& path,
467 int *height, int *width) {
468 SkFILEStream stream(path.c_str());
469 if (!stream.isValid()) {
470 SkDebugf("ERROR: couldn't open file <%s>\n",
471 path.c_str());
472 return false;
473 }
474
475 SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
476 if (NULL == codec) {
477 SkDebugf("ERROR: no codec found for <%s>\n",
478 path.c_str());
479 return false;
480 }
481
482 SkAutoTDelete<SkImageDecoder> ad(codec);
483 SkBitmap bm;
484
485 stream.rewind();
486 if (!codec->decode(&stream, &bm,
487 SkBitmap::kARGB_8888_Config,
488 SkImageDecoder::kDecodePixels_Mode)) {
489 SkDebugf("ERROR: codec failed for <%s>\n",
490 path.c_str());
491 return false;
492 }
493
494 *height = bm.height();
495 *width = bm.width();
496
497 return true;
498}
499
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000500// from gm - thanks to PNG, we need to force all pixels 100% opaque
501static void force_all_opaque(const SkBitmap& bitmap) {
502 SkAutoLockPixels lock(bitmap);
503 for (int y = 0; y < bitmap.height(); y++) {
504 for (int x = 0; x < bitmap.width(); x++) {
505 *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
506 }
507 }
508}
509
510// from gm
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000511static bool write_bitmap(const SkString& path, const SkBitmap* bitmap) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000512 SkBitmap copy;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000513 bitmap->copyTo(&copy, SkBitmap::kARGB_8888_Config);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000514 force_all_opaque(copy);
515 return SkImageEncoder::EncodeFile(path.c_str(), copy,
516 SkImageEncoder::kPNG_Type, 100);
517}
518
519// from gm
520static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
521 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
522 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
523 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
524
525 return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
526}
527
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000528static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1,
529 const int threshold) {
530 int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
531 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
532 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
533 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
534
535 return ((SkAbs32(da) <= threshold) &&
536 (SkAbs32(dr) <= threshold) &&
537 (SkAbs32(dg) <= threshold) &&
538 (SkAbs32(db) <= threshold));
539}
540
541// based on gm
epoger@google.com66008522012-05-16 17:40:57 +0000542// Postcondition: when we exit this method, dr->fResult should have some value
543// other than kUnknown.
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000544static void compute_diff(DiffRecord* dr,
545 DiffMetricProc diffFunction,
546 const int colorThreshold) {
epoger@google.com25d961c2012-02-02 20:50:36 +0000547 SkAutoLockPixels alpDiff(*dr->fDifferenceBitmap);
548 SkAutoLockPixels alpWhite(*dr->fWhiteBitmap);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000549
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000550 const int w = dr->fComparisonBitmap->width();
551 const int h = dr->fComparisonBitmap->height();
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000552 int mismatchedPixels = 0;
553 int totalMismatchR = 0;
554 int totalMismatchG = 0;
555 int totalMismatchB = 0;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000556
557 if (w != dr->fBaseWidth || h != dr->fBaseHeight) {
epoger@google.com292aff62012-05-16 14:57:28 +0000558 dr->fResult = kDifferentSizes;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000559 return;
560 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000561 // Accumulate fractionally different pixels, then divide out
562 // # of pixels at the end.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000563 dr->fWeightedFraction = 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000564 for (int y = 0; y < h; y++) {
565 for (int x = 0; x < w; x++) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000566 SkPMColor c0 = *dr->fBaseBitmap->getAddr32(x, y);
567 SkPMColor c1 = *dr->fComparisonBitmap->getAddr32(x, y);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000568 SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
569 SkPMColor outputDifference = diffFunction(c0, c1);
570 uint32_t thisR = SkGetPackedR32(trueDifference);
571 uint32_t thisG = SkGetPackedG32(trueDifference);
572 uint32_t thisB = SkGetPackedB32(trueDifference);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000573 totalMismatchR += thisR;
574 totalMismatchG += thisG;
575 totalMismatchB += thisB;
576 // In HSV, value is defined as max RGB component.
577 int value = MAX3(thisR, thisG, thisB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000578 dr->fWeightedFraction += ((float) value) / 255;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000579 if (thisR > dr->fMaxMismatchR) {
580 dr->fMaxMismatchR = thisR;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000581 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000582 if (thisG > dr->fMaxMismatchG) {
583 dr->fMaxMismatchG = thisG;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000584 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000585 if (thisB > dr->fMaxMismatchB) {
586 dr->fMaxMismatchB = thisB;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000587 }
588 if (!colors_match_thresholded(c0, c1, colorThreshold)) {
589 mismatchedPixels++;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000590 *dr->fDifferenceBitmap->getAddr32(x, y) = outputDifference;
epoger@google.com25d961c2012-02-02 20:50:36 +0000591 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_WHITE;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000592 } else {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000593 *dr->fDifferenceBitmap->getAddr32(x, y) = 0;
epoger@google.com25d961c2012-02-02 20:50:36 +0000594 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_BLACK;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000595 }
596 }
597 }
epoger@google.com292aff62012-05-16 14:57:28 +0000598 if (0 == mismatchedPixels) {
599 dr->fResult = kEqualPixels;
600 return;
601 }
602 dr->fResult = kDifferentPixels;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000603 int pixelCount = w * h;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000604 dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
605 dr->fWeightedFraction /= pixelCount;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000606 dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
607 dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
608 dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000609}
610
epoger@google.com25d961c2012-02-02 20:50:36 +0000611static SkString filename_to_derived_filename (const SkString& filename,
612 const char *suffix) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000613 SkString diffName (filename);
614 const char* cstring = diffName.c_str();
615 int dotOffset = strrchr(cstring, '.') - cstring;
616 diffName.remove(dotOffset, diffName.size() - dotOffset);
epoger@google.com25d961c2012-02-02 20:50:36 +0000617 diffName.append(suffix);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000618 return diffName;
619}
620
epoger@google.com25d961c2012-02-02 20:50:36 +0000621/// Given a image filename, returns the name of the file containing the
622/// associated difference image.
623static SkString filename_to_diff_filename (const SkString& filename) {
624 return filename_to_derived_filename(filename, "-diff.png");
625}
626
627/// Given a image filename, returns the name of the file containing the
628/// "white" difference image.
629static SkString filename_to_white_filename (const SkString& filename) {
630 return filename_to_derived_filename(filename, "-white.png");
631}
632
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000633static void release_bitmaps(DiffRecord* drp) {
634 delete drp->fBaseBitmap;
635 drp->fBaseBitmap = NULL;
636 delete drp->fComparisonBitmap;
637 drp->fComparisonBitmap = NULL;
638 delete drp->fDifferenceBitmap;
639 drp->fDifferenceBitmap = NULL;
epoger@google.com25d961c2012-02-02 20:50:36 +0000640 delete drp->fWhiteBitmap;
641 drp->fWhiteBitmap = NULL;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000642}
643
tomhudson@google.com7d042802011-07-14 13:15:55 +0000644
epoger@google.coma5f406e2012-05-01 13:26:16 +0000645/// If outputDir.isEmpty(), don't write out diff files.
tomhudson@google.com7d042802011-07-14 13:15:55 +0000646static void create_and_write_diff_image(DiffRecord* drp,
647 DiffMetricProc dmp,
648 const int colorThreshold,
649 const SkString& outputDir,
650 const SkString& filename) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000651 const int w = drp->fBaseWidth;
652 const int h = drp->fBaseHeight;
653 drp->fDifferenceBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
654 drp->fDifferenceBitmap->allocPixels();
epoger@google.com25d961c2012-02-02 20:50:36 +0000655 drp->fWhiteBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
656 drp->fWhiteBitmap->allocPixels();
tomhudson@google.com7d042802011-07-14 13:15:55 +0000657
epoger@google.com66008522012-05-16 17:40:57 +0000658 SkASSERT(kUnknown == drp->fResult);
659 compute_diff(drp, dmp, colorThreshold);
660 SkASSERT(kUnknown != drp->fResult);
661
662 if ((kDifferentPixels == drp->fResult) && !outputDir.isEmpty()) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000663 SkString differencePath (outputDir);
664 differencePath.append(filename_to_diff_filename(filename));
665 write_bitmap(differencePath, drp->fDifferenceBitmap);
666 SkString whitePath (outputDir);
667 whitePath.append(filename_to_white_filename(filename));
668 write_bitmap(whitePath, drp->fWhiteBitmap);
669 }
epoger@google.com66008522012-05-16 17:40:57 +0000670
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000671 release_bitmaps(drp);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000672}
673
epoger@google.coma5f406e2012-05-01 13:26:16 +0000674/// Returns true if string contains any of these substrings.
675static bool string_contains_any_of(const SkString& string,
676 const StringArray& substrings) {
677 for (int i = 0; i < substrings.count(); i++) {
678 if (string.contains(substrings[i]->c_str())) {
679 return true;
680 }
681 }
682 return false;
683}
684
685/// Iterate over dir and get all files that:
686/// - match any of the substrings in matchSubstrings, but...
687/// - DO NOT match any of the substrings in nomatchSubstrings
688/// Returns the list of files in *files.
689static void get_file_list(const SkString& dir,
690 const StringArray& matchSubstrings,
691 const StringArray& nomatchSubstrings,
692 FileArray *files) {
epoger@google.com5fd53852012-03-22 18:20:06 +0000693 SkOSFile::Iter it(dir.c_str());
694 SkString filename;
695 while (it.next(&filename)) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000696 if (string_contains_any_of(filename, matchSubstrings) &&
697 !string_contains_any_of(filename, nomatchSubstrings)) {
698 files->push(new SkString(filename));
epoger@google.com5fd53852012-03-22 18:20:06 +0000699 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000700 }
701}
702
703static void release_file_list(FileArray *files) {
704 files->deleteAll();
705}
706
707/// Comparison routines for qsort, sort by file names.
708static int compare_file_name_metrics(SkString **lhs, SkString **rhs) {
709 return strcmp((*lhs)->c_str(), (*rhs)->c_str());
710}
711
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000712/// Creates difference images, returns the number that have a 0 metric.
epoger@google.coma5f406e2012-05-01 13:26:16 +0000713/// If outputDir.isEmpty(), don't write out diff files.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000714static void create_diff_images (DiffMetricProc dmp,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000715 const int colorThreshold,
716 RecordArray* differences,
717 const SkString& baseDir,
718 const SkString& comparisonDir,
719 const SkString& outputDir,
epoger@google.coma5f406e2012-05-01 13:26:16 +0000720 const StringArray& matchSubstrings,
721 const StringArray& nomatchSubstrings,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000722 DiffSummary* summary) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000723 SkASSERT(!baseDir.isEmpty());
724 SkASSERT(!comparisonDir.isEmpty());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000725
epoger@google.com5fd53852012-03-22 18:20:06 +0000726 FileArray baseFiles;
727 FileArray comparisonFiles;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000728
epoger@google.coma5f406e2012-05-01 13:26:16 +0000729 get_file_list(baseDir, matchSubstrings, nomatchSubstrings, &baseFiles);
730 get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings,
731 &comparisonFiles);
epoger@google.com5fd53852012-03-22 18:20:06 +0000732
epoger@google.coma5f406e2012-05-01 13:26:16 +0000733 if (!baseFiles.isEmpty()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +0000734 qsort(baseFiles.begin(), baseFiles.count(), sizeof(SkString*),
735 SkCastForQSort(compare_file_name_metrics));
epoger@google.coma5f406e2012-05-01 13:26:16 +0000736 }
737 if (!comparisonFiles.isEmpty()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +0000738 qsort(comparisonFiles.begin(), comparisonFiles.count(),
739 sizeof(SkString*), SkCastForQSort(compare_file_name_metrics));
epoger@google.coma5f406e2012-05-01 13:26:16 +0000740 }
epoger@google.com66008522012-05-16 17:40:57 +0000741
epoger@google.com5fd53852012-03-22 18:20:06 +0000742 int i = 0;
743 int j = 0;
744
745 while (i < baseFiles.count() &&
746 j < comparisonFiles.count()) {
747
tomhudson@google.com4e305982011-07-13 17:42:46 +0000748 SkString basePath (baseDir);
epoger@google.com5fd53852012-03-22 18:20:06 +0000749 basePath.append(*baseFiles[i]);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000750 SkString comparisonPath (comparisonDir);
epoger@google.com5fd53852012-03-22 18:20:06 +0000751 comparisonPath.append(*comparisonFiles[j]);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000752
epoger@google.com5fd53852012-03-22 18:20:06 +0000753 DiffRecord *drp = NULL;
754 int v = strcmp(baseFiles[i]->c_str(),
755 comparisonFiles[j]->c_str());
756
757 if (v < 0) {
758 // in baseDir, but not in comparisonDir
epoger@google.com292aff62012-05-16 14:57:28 +0000759 drp = new DiffRecord(*baseFiles[i], basePath, comparisonPath,
760 kComparisonMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000761 ++i;
762 } else if (v > 0) {
763 // in comparisonDir, but not in baseDir
epoger@google.com292aff62012-05-16 14:57:28 +0000764 drp = new DiffRecord(*comparisonFiles[j], basePath, comparisonPath,
765 kBaseMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000766 ++j;
767 } else {
epoger@google.com46256ea2012-05-22 13:45:35 +0000768 // Found the same filename in both baseDir and comparisonDir.
epoger@google.com5fd53852012-03-22 18:20:06 +0000769 drp = new DiffRecord(*baseFiles[i], basePath, comparisonPath);
epoger@google.com46256ea2012-05-22 13:45:35 +0000770 SkASSERT(kUnknown == drp->fResult);
epoger@google.com5fd53852012-03-22 18:20:06 +0000771
epoger@google.com46256ea2012-05-22 13:45:35 +0000772 SkData* baseFileBits;
773 SkData* comparisonFileBits;
774 if (NULL == (baseFileBits = read_file(basePath.c_str()))) {
775 SkDebugf("WARNING: couldn't read base file <%s>\n",
776 basePath.c_str());
777 drp->fResult = kBaseMissing;
778 } else if (NULL == (comparisonFileBits = read_file(
779 comparisonPath.c_str()))) {
780 SkDebugf("WARNING: couldn't read comparison file <%s>\n",
781 comparisonPath.c_str());
782 drp->fResult = kComparisonMissing;
783 } else {
784 if (are_buffers_equal(baseFileBits, comparisonFileBits)) {
785 drp->fResult = kEqualBits;
786 } else if (get_bitmaps(baseFileBits, comparisonFileBits, drp)) {
787 create_and_write_diff_image(drp, dmp, colorThreshold,
788 outputDir, *baseFiles[i]);
789 } else {
790 drp->fResult = kDifferentOther;
791 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000792 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000793 if (baseFileBits) {
794 baseFileBits->unref();
795 }
796 if (comparisonFileBits) {
797 comparisonFileBits->unref();
798 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000799 ++i;
800 ++j;
801 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000802 SkASSERT(kUnknown != drp->fResult);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000803 differences->push(drp);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000804 summary->add(drp);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000805 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000806
807 for (; i < baseFiles.count(); ++i) {
808 // files only in baseDir
809 SkString basePath (baseDir);
810 basePath.append(*baseFiles[i]);
811 SkString comparisonPath;
812 DiffRecord *drp = new DiffRecord(*baseFiles[i], basePath,
epoger@google.com292aff62012-05-16 14:57:28 +0000813 comparisonPath, kComparisonMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000814 differences->push(drp);
815 summary->add(drp);
816 }
817
818 for (; j < comparisonFiles.count(); ++j) {
819 // files only in comparisonDir
820 SkString basePath;
821 SkString comparisonPath(comparisonDir);
822 comparisonPath.append(*comparisonFiles[j]);
823 DiffRecord *drp = new DiffRecord(*comparisonFiles[j], basePath,
epoger@google.com292aff62012-05-16 14:57:28 +0000824 comparisonPath, kBaseMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000825 differences->push(drp);
826 summary->add(drp);
827 }
828
829 release_file_list(&baseFiles);
830 release_file_list(&comparisonFiles);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000831}
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000832
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000833/// Make layout more consistent by scaling image to 240 height, 360 width,
834/// or natural size, whichever is smallest.
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000835static int compute_image_height (int height, int width) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000836 int retval = 240;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000837 if (height < retval) {
838 retval = height;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000839 }
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000840 float scale = (float) retval / height;
841 if (width * scale > 360) {
842 scale = (float) 360 / width;
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000843 retval = static_cast<int>(height * scale);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000844 }
845 return retval;
846}
847
epoger@google.com25d961c2012-02-02 20:50:36 +0000848static void print_table_header (SkFILEWStream* stream,
849 const int matchCount,
850 const int colorThreshold,
851 const RecordArray& differences,
852 const SkString &baseDir,
epoger@google.coma2b793c2012-05-15 14:58:53 +0000853 const SkString &comparisonDir,
854 bool doOutputDate=false) {
epoger@google.com25d961c2012-02-02 20:50:36 +0000855 stream->writeText("<table>\n");
856 stream->writeText("<tr><th>");
epoger@google.coma2b793c2012-05-15 14:58:53 +0000857 if (doOutputDate) {
858 SkTime::DateTime dt;
859 SkTime::GetDateTime(&dt);
860 stream->writeText("SkDiff run at ");
861 stream->writeDecAsText(dt.fHour);
862 stream->writeText(":");
863 if (dt.fMinute < 10) {
864 stream->writeText("0");
865 }
866 stream->writeDecAsText(dt.fMinute);
867 stream->writeText(":");
868 if (dt.fSecond < 10) {
869 stream->writeText("0");
870 }
871 stream->writeDecAsText(dt.fSecond);
872 stream->writeText("<br>");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000873 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000874 stream->writeDecAsText(matchCount);
875 stream->writeText(" of ");
876 stream->writeDecAsText(differences.count());
877 stream->writeText(" images matched ");
878 if (colorThreshold == 0) {
879 stream->writeText("exactly");
880 } else {
881 stream->writeText("within ");
882 stream->writeDecAsText(colorThreshold);
883 stream->writeText(" color units per component");
884 }
885 stream->writeText(".<br>");
epoger@google.com25d961c2012-02-02 20:50:36 +0000886 stream->writeText("</th>\n<th>");
887 stream->writeText("every different pixel shown in white");
888 stream->writeText("</th>\n<th>");
889 stream->writeText("color difference at each pixel");
epoger@google.com46a45962012-07-12 18:16:02 +0000890 stream->writeText("</th>\n<th>baseDir: ");
epoger@google.com25d961c2012-02-02 20:50:36 +0000891 stream->writeText(baseDir.c_str());
epoger@google.com46a45962012-07-12 18:16:02 +0000892 stream->writeText("</th>\n<th>comparisonDir: ");
epoger@google.com25d961c2012-02-02 20:50:36 +0000893 stream->writeText(comparisonDir.c_str());
894 stream->writeText("</th>\n");
895 stream->writeText("</tr>\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000896}
897
898static void print_pixel_count (SkFILEWStream* stream,
899 const DiffRecord& diff) {
900 stream->writeText("<br>(");
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000901 stream->writeDecAsText(static_cast<int>(diff.fFractionDifference *
902 diff.fBaseWidth *
903 diff.fBaseHeight));
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000904 stream->writeText(" pixels)");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000905/*
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000906 stream->writeDecAsText(diff.fWeightedFraction *
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000907 diff.fBaseWidth *
908 diff.fBaseHeight);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000909 stream->writeText(" weighted pixels)");
910*/
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000911}
912
913static void print_label_cell (SkFILEWStream* stream,
914 const DiffRecord& diff) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000915 char metricBuf [20];
916
917 stream->writeText("<td><b>");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000918 stream->writeText(diff.fFilename.c_str());
epoger@google.com46256ea2012-05-22 13:45:35 +0000919 stream->writeText("</b><br>");
epoger@google.com292aff62012-05-16 14:57:28 +0000920 switch (diff.fResult) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000921 case kEqualBits:
922 SkDEBUGFAIL("should not encounter DiffRecord with kEqualBits here");
923 return;
924 case kEqualPixels:
925 SkDEBUGFAIL("should not encounter DiffRecord with kEqualPixels here");
epoger@google.com5fd53852012-03-22 18:20:06 +0000926 return;
epoger@google.com292aff62012-05-16 14:57:28 +0000927 case kDifferentSizes:
epoger@google.com46256ea2012-05-22 13:45:35 +0000928 stream->writeText("Image sizes differ</td>");
929 return;
930 case kDifferentPixels:
931 sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
932 stream->writeText(metricBuf);
933 stream->writeText(" of pixels differ");
934 stream->writeText("\n (");
935 sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
936 stream->writeText(metricBuf);
937 stream->writeText(" weighted)");
938 // Write the actual number of pixels that differ if it's < 1%
939 if (diff.fFractionDifference < 0.01) {
940 print_pixel_count(stream, diff);
941 }
942 stream->writeText("<br>Average color mismatch ");
943 stream->writeDecAsText(static_cast<int>(MAX3(diff.fAverageMismatchR,
944 diff.fAverageMismatchG,
945 diff.fAverageMismatchB)));
946 stream->writeText("<br>Max color mismatch ");
947 stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
948 diff.fMaxMismatchG,
949 diff.fMaxMismatchB));
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000950 stream->writeText("</td>");
epoger@google.com46256ea2012-05-22 13:45:35 +0000951 break;
952 case kDifferentOther:
953 stream->writeText("Files differ; unable to parse one or both files</td>");
954 return;
955 case kBaseMissing:
956 stream->writeText("Missing from baseDir</td>");
957 return;
958 case kComparisonMissing:
959 stream->writeText("Missing from comparisonDir</td>");
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000960 return;
epoger@google.com292aff62012-05-16 14:57:28 +0000961 default:
epoger@google.com46256ea2012-05-22 13:45:35 +0000962 SkDEBUGFAIL("encountered DiffRecord with unknown result type");
963 return;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000964 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000965}
966
967static void print_image_cell (SkFILEWStream* stream,
tomhudson@google.com4e305982011-07-13 17:42:46 +0000968 const SkString& path,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000969 int height) {
970 stream->writeText("<td><a href=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000971 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000972 stream->writeText("\"><img src=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000973 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000974 stream->writeText("\" height=\"");
975 stream->writeDecAsText(height);
976 stream->writeText("px\"></a></td>");
977}
978
caryclark@google.com3dd45912012-06-06 12:11:10 +0000979#if 0 // UNUSED
epoger@google.com01f78702012-04-12 16:32:04 +0000980static void print_text_cell (SkFILEWStream* stream, const char* text) {
981 stream->writeText("<td align=center>");
982 if (NULL != text) {
983 stream->writeText(text);
984 }
985 stream->writeText("</td>");
986}
caryclark@google.com3dd45912012-06-06 12:11:10 +0000987#endif
epoger@google.com01f78702012-04-12 16:32:04 +0000988
epoger@google.com5fd53852012-03-22 18:20:06 +0000989static void print_diff_with_missing_file(SkFILEWStream* stream,
990 DiffRecord& diff,
991 const SkString& relativePath) {
992 stream->writeText("<tr>\n");
993 print_label_cell(stream, diff);
994 stream->writeText("<td>N/A</td>");
995 stream->writeText("<td>N/A</td>");
epoger@google.com292aff62012-05-16 14:57:28 +0000996 if (kBaseMissing != diff.fResult) {
epoger@google.com5fd53852012-03-22 18:20:06 +0000997 int h, w;
998 if (!get_bitmap_height_width(diff.fBasePath, &h, &w)) {
999 stream->writeText("<td>N/A</td>");
1000 } else {
1001 int height = compute_image_height(h, w);
1002 if (!diff.fBasePath.startsWith(PATH_DIV_STR)) {
1003 diff.fBasePath.prepend(relativePath);
1004 }
1005 print_image_cell(stream, diff.fBasePath, height);
1006 }
1007 } else {
1008 stream->writeText("<td>N/A</td>");
1009 }
epoger@google.com292aff62012-05-16 14:57:28 +00001010 if (kComparisonMissing != diff.fResult) {
epoger@google.com5fd53852012-03-22 18:20:06 +00001011 int h, w;
1012 if (!get_bitmap_height_width(diff.fComparisonPath, &h, &w)) {
1013 stream->writeText("<td>N/A</td>");
1014 } else {
1015 int height = compute_image_height(h, w);
1016 if (!diff.fComparisonPath.startsWith(PATH_DIV_STR)) {
1017 diff.fComparisonPath.prepend(relativePath);
1018 }
1019 print_image_cell(stream, diff.fComparisonPath, height);
1020 }
1021 } else {
1022 stream->writeText("<td>N/A</td>");
1023 }
1024 stream->writeText("</tr>\n");
1025 stream->flush();
1026}
1027
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001028static void print_diff_page (const int matchCount,
1029 const int colorThreshold,
1030 const RecordArray& differences,
1031 const SkString& baseDir,
1032 const SkString& comparisonDir,
1033 const SkString& outputDir) {
1034
epoger@google.coma5f406e2012-05-01 13:26:16 +00001035 SkASSERT(!baseDir.isEmpty());
1036 SkASSERT(!comparisonDir.isEmpty());
1037 SkASSERT(!outputDir.isEmpty());
1038
tomhudson@google.com5b325292011-05-24 19:41:13 +00001039 SkString outputPath (outputDir);
1040 outputPath.append("index.html");
1041 //SkFILEWStream outputStream ("index.html");
1042 SkFILEWStream outputStream (outputPath.c_str());
1043
tomhudson@google.com4e305982011-07-13 17:42:46 +00001044 // Need to convert paths from relative-to-cwd to relative-to-outputDir
tomhudson@google.com5b325292011-05-24 19:41:13 +00001045 // FIXME this doesn't work if there are '..' inside the outputDir
1046 unsigned int ui;
1047 SkString relativePath;
1048 for (ui = 0; ui < outputDir.size(); ui++) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001049 if (outputDir[ui] == PATH_DIV_CHAR) {
1050 relativePath.append(".." PATH_DIV_STR);
tomhudson@google.com5b325292011-05-24 19:41:13 +00001051 }
1052 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001053
1054 outputStream.writeText("<html>\n<body>\n");
epoger@google.com25d961c2012-02-02 20:50:36 +00001055 print_table_header(&outputStream, matchCount, colorThreshold, differences,
1056 baseDir, comparisonDir);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001057 int i;
1058 for (i = 0; i < differences.count(); i++) {
1059 DiffRecord* diff = differences[i];
epoger@google.com5fd53852012-03-22 18:20:06 +00001060
epoger@google.com292aff62012-05-16 14:57:28 +00001061 switch (diff->fResult) {
epoger@google.com46256ea2012-05-22 13:45:35 +00001062 // Cases in which there is no diff to report.
1063 case kEqualBits:
epoger@google.com292aff62012-05-16 14:57:28 +00001064 case kEqualPixels:
1065 continue;
epoger@google.com46256ea2012-05-22 13:45:35 +00001066 // Cases in which we want a detailed pixel diff.
1067 case kDifferentPixels:
1068 break;
1069 // Cases in which the files differed, but we can't display the diff.
1070 case kDifferentSizes:
1071 case kDifferentOther:
epoger@google.com292aff62012-05-16 14:57:28 +00001072 case kBaseMissing:
epoger@google.com292aff62012-05-16 14:57:28 +00001073 case kComparisonMissing:
epoger@google.com5fd53852012-03-22 18:20:06 +00001074 print_diff_with_missing_file(&outputStream, *diff, relativePath);
1075 continue;
epoger@google.com292aff62012-05-16 14:57:28 +00001076 default:
epoger@google.com46256ea2012-05-22 13:45:35 +00001077 SkDEBUGFAIL("encountered DiffRecord with unknown result type");
1078 continue;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001079 }
epoger@google.com5fd53852012-03-22 18:20:06 +00001080
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001081 if (!diff->fBasePath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +00001082 diff->fBasePath.prepend(relativePath);
1083 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001084 if (!diff->fComparisonPath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +00001085 diff->fComparisonPath.prepend(relativePath);
1086 }
epoger@google.com5fd53852012-03-22 18:20:06 +00001087
tomhudson@google.com9b540ce2011-08-02 14:10:04 +00001088 int height = compute_image_height(diff->fBaseHeight, diff->fBaseWidth);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001089 outputStream.writeText("<tr>\n");
1090 print_label_cell(&outputStream, *diff);
epoger@google.com46256ea2012-05-22 13:45:35 +00001091 print_image_cell(&outputStream,
1092 filename_to_white_filename(diff->fFilename), height);
1093 print_image_cell(&outputStream,
1094 filename_to_diff_filename(diff->fFilename), height);
epoger@google.com25d961c2012-02-02 20:50:36 +00001095 print_image_cell(&outputStream, diff->fBasePath, height);
tomhudson@google.com4e305982011-07-13 17:42:46 +00001096 print_image_cell(&outputStream, diff->fComparisonPath, height);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001097 outputStream.writeText("</tr>\n");
1098 outputStream.flush();
1099 }
1100 outputStream.writeText("</table>\n");
1101 outputStream.writeText("</body>\n</html>\n");
1102 outputStream.flush();
1103}
1104
1105static void usage (char * argv0) {
1106 SkDebugf("Skia baseline image diff tool\n");
epoger@google.coma5f406e2012-05-01 13:26:16 +00001107 SkDebugf("\n"
1108"Usage: \n"
1109" %s <baseDir> <comparisonDir> [outputDir] \n"
epoger@google.coma611c3e2012-05-18 20:10:06 +00001110, argv0, argv0);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001111 SkDebugf(
epoger@google.com46a45962012-07-12 18:16:02 +00001112"\nArguments:"
epoger@google.comdfbf24e2012-07-13 21:22:02 +00001113"\n --failonresult <result>: After comparing all file pairs, exit with nonzero"
1114"\n return code (number of file pairs yielding this"
1115"\n result) if any file pairs yielded this result."
1116"\n This flag may be repeated, in which case the"
1117"\n return code will be the number of fail pairs"
1118"\n yielding ANY of these results."
epoger@google.com46a45962012-07-12 18:16:02 +00001119"\n --help: display this info"
epoger@google.com46a45962012-07-12 18:16:02 +00001120"\n --listfilenames: list all filenames for each result type in stdout"
epoger@google.comdfbf24e2012-07-13 21:22:02 +00001121"\n --match <substring>: compare files whose filenames contain this substring;"
1122"\n if unspecified, compare ALL files."
1123"\n this flag may be repeated."
epoger@google.com46a45962012-07-12 18:16:02 +00001124"\n --nodiffs: don't write out image diffs or index.html, just generate"
1125"\n report on stdout"
epoger@google.comdfbf24e2012-07-13 21:22:02 +00001126"\n --nomatch <substring>: regardless of --match, DO NOT compare files whose"
1127"\n filenames contain this substring."
1128"\n this flag may be repeated."
epoger@google.com46a45962012-07-12 18:16:02 +00001129"\n --noprintdirs: do not print the directories used."
1130"\n --sortbymaxmismatch: sort by worst color channel mismatch;"
1131"\n break ties with -sortbymismatch"
1132"\n --sortbymismatch: sort by average color channel mismatch"
1133"\n --threshold <n>: only report differences > n (per color channel) [default 0]"
1134"\n --weighted: sort by # pixels different weighted by color difference"
1135"\n"
1136"\n baseDir: directory to read baseline images from."
1137"\n comparisonDir: directory to read comparison images from"
1138"\n outputDir: directory to write difference images and index.html to;"
1139"\n defaults to comparisonDir"
1140"\n"
1141"\nIf no sort is specified, it will sort by fraction of pixels mismatching."
1142"\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001143}
1144
epoger@google.com70044cc2012-07-12 18:37:55 +00001145const int kNoError = 0;
1146const int kGenericError = -1;
epoger@google.com46a45962012-07-12 18:16:02 +00001147
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001148int main (int argc, char ** argv) {
1149 DiffMetricProc diffProc = compute_diff_pmcolor;
epoger@google.com28060e72012-06-28 16:47:34 +00001150 int (*sortProc)(const void*, const void*) = compare<CompareDiffMetrics>;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001151
1152 // Maximum error tolerated in any one color channel in any one pixel before
1153 // a difference is reported.
1154 int colorThreshold = 0;
1155 SkString baseDir;
1156 SkString comparisonDir;
1157 SkString outputDir;
epoger@google.comdfbf24e2012-07-13 21:22:02 +00001158
epoger@google.coma5f406e2012-05-01 13:26:16 +00001159 StringArray matchSubstrings;
1160 StringArray nomatchSubstrings;
epoger@google.comdfbf24e2012-07-13 21:22:02 +00001161 SkTDArray<Result> failOnTheseResultTypes;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001162
epoger@google.coma5f406e2012-05-01 13:26:16 +00001163 bool generateDiffs = true;
epoger@google.com46a45962012-07-12 18:16:02 +00001164 bool listFilenames = false;
keyar@chromium.orga6318192012-07-09 21:01:50 +00001165 bool printDirs = true;
tomhudson@google.com7d042802011-07-14 13:15:55 +00001166
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001167 RecordArray differences;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001168 DiffSummary summary;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001169
epoger@google.coma5f406e2012-05-01 13:26:16 +00001170 int i;
1171 int numUnflaggedArguments = 0;
1172 for (i = 1; i < argc; i++) {
epoger@google.comdfbf24e2012-07-13 21:22:02 +00001173 if (!strcmp(argv[i], "--failonresult")) {
1174 Result type = getResultByName(argv[++i]);
1175 if (!failOnTheseResultTypes.contains(type)) {
1176 failOnTheseResultTypes.push(type);
1177 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001178 continue;
1179 }
epoger@google.com46a45962012-07-12 18:16:02 +00001180 if (!strcmp(argv[i], "--help")) {
1181 usage(argv[0]);
epoger@google.com70044cc2012-07-12 18:37:55 +00001182 return kNoError;
epoger@google.com46a45962012-07-12 18:16:02 +00001183 }
1184 if (!strcmp(argv[i], "--listfilenames")) {
1185 listFilenames = true;
epoger@google.coma5f406e2012-05-01 13:26:16 +00001186 continue;
1187 }
1188 if (!strcmp(argv[i], "--match")) {
1189 matchSubstrings.push(new SkString(argv[++i]));
1190 continue;
1191 }
epoger@google.com46a45962012-07-12 18:16:02 +00001192 if (!strcmp(argv[i], "--nodiffs")) {
1193 generateDiffs = false;
1194 continue;
1195 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001196 if (!strcmp(argv[i], "--nomatch")) {
1197 nomatchSubstrings.push(new SkString(argv[++i]));
1198 continue;
1199 }
epoger@google.com46a45962012-07-12 18:16:02 +00001200 if (!strcmp(argv[i], "--noprintdirs")) {
1201 printDirs = false;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001202 continue;
1203 }
tomhudson@google.com7d042802011-07-14 13:15:55 +00001204 if (!strcmp(argv[i], "--sortbymaxmismatch")) {
epoger@google.com28060e72012-06-28 16:47:34 +00001205 sortProc = compare<CompareDiffMaxMismatches>;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001206 continue;
1207 }
epoger@google.com46a45962012-07-12 18:16:02 +00001208 if (!strcmp(argv[i], "--sortbymismatch")) {
1209 sortProc = compare<CompareDiffMeanMismatches>;
tomhudson@google.com5b325292011-05-24 19:41:13 +00001210 continue;
1211 }
epoger@google.com46a45962012-07-12 18:16:02 +00001212 if (!strcmp(argv[i], "--threshold")) {
1213 colorThreshold = atoi(argv[++i]);
1214 continue;
1215 }
1216 if (!strcmp(argv[i], "--weighted")) {
1217 sortProc = compare<CompareDiffWeighted>;
keyar@chromium.orga6318192012-07-09 21:01:50 +00001218 continue;
1219 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001220 if (argv[i][0] != '-') {
epoger@google.coma5f406e2012-05-01 13:26:16 +00001221 switch (numUnflaggedArguments++) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001222 case 0:
1223 baseDir.set(argv[i]);
1224 continue;
1225 case 1:
1226 comparisonDir.set(argv[i]);
1227 continue;
1228 case 2:
1229 outputDir.set(argv[i]);
1230 continue;
1231 default:
epoger@google.coma5f406e2012-05-01 13:26:16 +00001232 SkDebugf("extra unflagged argument <%s>\n", argv[i]);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001233 usage(argv[0]);
epoger@google.com70044cc2012-07-12 18:37:55 +00001234 return kGenericError;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001235 }
1236 }
1237
1238 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
1239 usage(argv[0]);
epoger@google.com70044cc2012-07-12 18:37:55 +00001240 return kGenericError;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001241 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001242
epoger@google.coma5f406e2012-05-01 13:26:16 +00001243 if (numUnflaggedArguments == 2) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001244 outputDir = comparisonDir;
epoger@google.coma5f406e2012-05-01 13:26:16 +00001245 } else if (numUnflaggedArguments != 3) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001246 usage(argv[0]);
epoger@google.com70044cc2012-07-12 18:37:55 +00001247 return kGenericError;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001248 }
1249
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001250 if (!baseDir.endsWith(PATH_DIV_STR)) {
1251 baseDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001252 }
keyar@chromium.orga6318192012-07-09 21:01:50 +00001253 if (printDirs) {
1254 printf("baseDir is [%s]\n", baseDir.c_str());
1255 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001256
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001257 if (!comparisonDir.endsWith(PATH_DIV_STR)) {
1258 comparisonDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001259 }
keyar@chromium.orga6318192012-07-09 21:01:50 +00001260 if (printDirs) {
1261 printf("comparisonDir is [%s]\n", comparisonDir.c_str());
1262 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001263
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001264 if (!outputDir.endsWith(PATH_DIV_STR)) {
1265 outputDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001266 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001267 if (generateDiffs) {
keyar@chromium.orga6318192012-07-09 21:01:50 +00001268 if (printDirs) {
1269 printf("writing diffs to outputDir is [%s]\n", outputDir.c_str());
1270 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001271 } else {
keyar@chromium.orga6318192012-07-09 21:01:50 +00001272 if (printDirs) {
1273 printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str());
1274 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001275 outputDir.set("");
1276 }
1277
epoger@google.comda4af242012-06-25 18:45:50 +00001278 // If no matchSubstrings were specified, match ALL strings
1279 // (except for whatever nomatchSubstrings were specified, if any).
epoger@google.coma5f406e2012-05-01 13:26:16 +00001280 if (matchSubstrings.isEmpty()) {
1281 matchSubstrings.push(new SkString(""));
1282 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001283
epoger@google.coma611c3e2012-05-18 20:10:06 +00001284 create_diff_images(diffProc, colorThreshold, &differences,
1285 baseDir, comparisonDir, outputDir,
1286 matchSubstrings, nomatchSubstrings, &summary);
epoger@google.com46a45962012-07-12 18:16:02 +00001287 summary.print(listFilenames);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001288
1289 if (differences.count()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +00001290 qsort(differences.begin(), differences.count(),
1291 sizeof(DiffRecord*), sortProc);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001292 }
epoger@google.com66008522012-05-16 17:40:57 +00001293
epoger@google.coma5f406e2012-05-01 13:26:16 +00001294 if (generateDiffs) {
1295 print_diff_page(summary.fNumMatches, colorThreshold, differences,
1296 baseDir, comparisonDir, outputDir);
1297 }
epoger@google.com76222c02012-05-31 15:12:09 +00001298
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001299 for (i = 0; i < differences.count(); i++) {
1300 delete differences[i];
1301 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001302 matchSubstrings.deleteAll();
1303 nomatchSubstrings.deleteAll();
epoger@google.combe6188d2012-05-31 15:13:45 +00001304
epoger@google.comdfbf24e2012-07-13 21:22:02 +00001305 int num_failing_results = 0;
1306 for (int i = 0; i < failOnTheseResultTypes.count(); i++) {
1307 Result type = failOnTheseResultTypes[i];
1308 num_failing_results += summary.fResultsOfType[type].count();
epoger@google.com46a45962012-07-12 18:16:02 +00001309 }
epoger@google.comdfbf24e2012-07-13 21:22:02 +00001310 return num_failing_results;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001311}