blob: 9c327dfab658fa6f465cb5ffe7eb6e9fec3c3841 [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.com3af4ff42012-07-19 17:35:04 +0000196 // Print a line about the contents of this FileArray to stdout.
epoger@google.com46a45962012-07-12 18:16:02 +0000197 void printContents(const FileArray& fileArray, const char* headerText, bool listFilenames) {
epoger@google.com76222c02012-05-31 15:12:09 +0000198 int n = fileArray.count();
epoger@google.com3af4ff42012-07-19 17:35:04 +0000199 printf("%d file pairs %s", n, headerText);
200 if (listFilenames) {
201 printf(": ");
202 for (int i = 0; i < n; ++i) {
203 printf("%s ", fileArray[i]->c_str());
epoger@google.com76222c02012-05-31 15:12:09 +0000204 }
205 }
epoger@google.com3af4ff42012-07-19 17:35:04 +0000206 printf("\n");
epoger@google.com76222c02012-05-31 15:12:09 +0000207 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000208
epoger@google.com3af4ff42012-07-19 17:35:04 +0000209 void print(bool listFilenames, bool failOnResultType[kNumResultTypes]) {
210 printf("\ncompared %d file pairs:\n", fNumMatches + fNumMismatches);
epoger@google.com46a45962012-07-12 18:16:02 +0000211 for (int resultInt = 0; resultInt < kNumResultTypes; resultInt++) {
212 Result result = static_cast<Result>(resultInt);
epoger@google.com3af4ff42012-07-19 17:35:04 +0000213 if (failOnResultType[result]) {
214 printf("[*] ");
215 } else {
216 printf("[_] ");
217 }
epoger@google.com46a45962012-07-12 18:16:02 +0000218 printContents(fResultsOfType[result], getResultDescription(result), listFilenames);
219 }
epoger@google.com3af4ff42012-07-19 17:35:04 +0000220 printf("(results marked with [*] will cause nonzero return value)\n");
221 printf("\nnumber of mismatching file pairs: %d\n", fNumMismatches);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000222 if (fNumMismatches > 0) {
223 printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
epoger@google.com46a45962012-07-12 18:16:02 +0000224 printf("Largest area mismatch was %.2f%% of pixels\n",fMaxMismatchPercent);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000225 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000226 }
227
228 void add (DiffRecord* drp) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000229 uint32_t mismatchValue;
epoger@google.com292aff62012-05-16 14:57:28 +0000230
epoger@google.com76222c02012-05-31 15:12:09 +0000231 fResultsOfType[drp->fResult].push(new SkString(drp->fFilename));
epoger@google.com292aff62012-05-16 14:57:28 +0000232 switch (drp->fResult) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000233 case kEqualBits:
234 fNumMatches++;
235 break;
epoger@google.com292aff62012-05-16 14:57:28 +0000236 case kEqualPixels:
237 fNumMatches++;
238 break;
epoger@google.com46256ea2012-05-22 13:45:35 +0000239 case kDifferentSizes:
epoger@google.com5fd53852012-03-22 18:20:06 +0000240 fNumMismatches++;
epoger@google.com292aff62012-05-16 14:57:28 +0000241 break;
epoger@google.com46256ea2012-05-22 13:45:35 +0000242 case kDifferentPixels:
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000243 fNumMismatches++;
244 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
245 fMaxMismatchPercent = drp->fFractionDifference * 100;
246 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000247 mismatchValue = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
248 drp->fMaxMismatchB);
249 if (mismatchValue > fMaxMismatchV) {
250 fMaxMismatchV = mismatchValue;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000251 }
epoger@google.com292aff62012-05-16 14:57:28 +0000252 break;
epoger@google.com46256ea2012-05-22 13:45:35 +0000253 case kDifferentOther:
254 fNumMismatches++;
epoger@google.com46256ea2012-05-22 13:45:35 +0000255 break;
256 case kBaseMissing:
257 fNumMismatches++;
epoger@google.com46256ea2012-05-22 13:45:35 +0000258 break;
259 case kComparisonMissing:
260 fNumMismatches++;
epoger@google.com46256ea2012-05-22 13:45:35 +0000261 break;
262 case kUnknown:
263 SkDEBUGFAIL("adding uncategorized DiffRecord");
264 break;
265 default:
266 SkDEBUGFAIL("adding DiffRecord with unhandled fResult value");
267 break;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000268 }
269 }
270};
271
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000272typedef SkTDArray<DiffRecord*> RecordArray;
273
epoger@google.com28060e72012-06-28 16:47:34 +0000274/// A wrapper for any sortProc (comparison routine) which applies a first-order
275/// sort beforehand, and a tiebreaker if the sortProc returns 0.
276template<typename T>
277static int compare(const void* untyped_lhs, const void* untyped_rhs) {
278 const DiffRecord* lhs = *reinterpret_cast<DiffRecord* const*>(untyped_lhs);
279 const DiffRecord* rhs = *reinterpret_cast<DiffRecord* const*>(untyped_rhs);
280
281 // First-order sort... these comparisons should be applied before comparing
282 // pixel values, no matter what.
283 if (lhs->fResult != rhs->fResult) {
284 return (lhs->fResult < rhs->fResult) ? 1 : -1;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000285 }
epoger@google.com28060e72012-06-28 16:47:34 +0000286
287 // Passed first-order sort, so call the pixel comparison routine.
288 int result = T::comparePixels(lhs, rhs);
289 if (result != 0) {
290 return result;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000291 }
epoger@google.com28060e72012-06-28 16:47:34 +0000292
293 // Tiebreaker... if we got to this point, we don't really care
294 // which order they are sorted in, but let's at least be consistent.
295 return strcmp(lhs->fFilename.c_str(), rhs->fFilename.c_str());
tomhudson@google.com5b325292011-05-24 19:41:13 +0000296}
297
epoger@google.com28060e72012-06-28 16:47:34 +0000298/// Comparison routine for qsort; sorts by fFractionDifference
299/// from largest to smallest.
300class CompareDiffMetrics {
301public:
302 static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
303 if (lhs->fFractionDifference < rhs->fFractionDifference) {
304 return 1;
305 }
306 if (rhs->fFractionDifference < lhs->fFractionDifference) {
307 return -1;
308 }
309 return 0;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000310 }
epoger@google.com28060e72012-06-28 16:47:34 +0000311};
312
313class CompareDiffWeighted {
314public:
315 static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
316 if (lhs->fWeightedFraction < rhs->fWeightedFraction) {
317 return 1;
318 }
319 if (lhs->fWeightedFraction > rhs->fWeightedFraction) {
320 return -1;
321 }
322 return 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000323 }
epoger@google.com28060e72012-06-28 16:47:34 +0000324};
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000325
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000326/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
327/// from largest to smallest.
epoger@google.com28060e72012-06-28 16:47:34 +0000328class CompareDiffMeanMismatches {
329public:
330 static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
331 float leftValue = MAX3(lhs->fAverageMismatchR,
332 lhs->fAverageMismatchG,
333 lhs->fAverageMismatchB);
334 float rightValue = MAX3(rhs->fAverageMismatchR,
335 rhs->fAverageMismatchG,
336 rhs->fAverageMismatchB);
337 if (leftValue < rightValue) {
338 return 1;
339 }
340 if (rightValue < leftValue) {
341 return -1;
342 }
343 return 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000344 }
epoger@google.com28060e72012-06-28 16:47:34 +0000345};
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000346
347/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB})
348/// from largest to smallest.
epoger@google.com28060e72012-06-28 16:47:34 +0000349class CompareDiffMaxMismatches {
350public:
351 static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
352 uint32_t leftValue = MAX3(lhs->fMaxMismatchR,
353 lhs->fMaxMismatchG,
354 lhs->fMaxMismatchB);
355 uint32_t rightValue = MAX3(rhs->fMaxMismatchR,
356 rhs->fMaxMismatchG,
357 rhs->fMaxMismatchB);
358 if (leftValue < rightValue) {
359 return 1;
360 }
361 if (rightValue < leftValue) {
362 return -1;
363 }
364
365 return CompareDiffMeanMismatches::comparePixels(lhs, rhs);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000366 }
epoger@google.com28060e72012-06-28 16:47:34 +0000367};
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000368
369
370
371/// Parameterized routine to compute the color of a pixel in a difference image.
372typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
373
caryclark@google.com3dd45912012-06-06 12:11:10 +0000374#if 0 // UNUSED
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000375static void expand_and_copy (int width, int height, SkBitmap** dest) {
376 SkBitmap* temp = new SkBitmap ();
377 temp->reset();
378 temp->setConfig((*dest)->config(), width, height);
379 temp->allocPixels();
380 (*dest)->copyPixelsTo(temp->getPixels(), temp->getSize(),
381 temp->rowBytes());
382 *dest = temp;
383}
caryclark@google.com3dd45912012-06-06 12:11:10 +0000384#endif
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000385
epoger@google.com46256ea2012-05-22 13:45:35 +0000386/// Returns true if the two buffers passed in are both non-NULL, and include
387/// exactly the same byte values (and identical lengths).
388static bool are_buffers_equal(SkData* skdata1, SkData* skdata2) {
389 if ((NULL == skdata1) || (NULL == skdata2)) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000390 return false;
391 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000392 if (skdata1->size() != skdata2->size()) {
393 return false;
394 }
395 return (0 == memcmp(skdata1->data(), skdata2->data(), skdata1->size()));
396}
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000397
epoger@google.com46256ea2012-05-22 13:45:35 +0000398/// Reads the file at the given path and returns its complete contents as an
399/// SkData object (or returns NULL on error).
400static SkData* read_file(const char* file_path) {
401 SkFILEStream fileStream(file_path);
402 if (!fileStream.isValid()) {
403 SkDebugf("WARNING: could not open file <%s> for reading\n", file_path);
404 return NULL;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000405 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000406 size_t bytesInFile = fileStream.getLength();
407 size_t bytesLeftToRead = bytesInFile;
408
409 void* bufferStart = sk_malloc_throw(bytesInFile);
410 char* bufferPointer = (char*)bufferStart;
411 while (bytesLeftToRead > 0) {
412 size_t bytesReadThisTime = fileStream.read(
413 bufferPointer, bytesLeftToRead);
414 if (0 == bytesReadThisTime) {
415 SkDebugf("WARNING: error reading from <%s>\n", file_path);
416 sk_free(bufferStart);
417 return NULL;
418 }
419 bytesLeftToRead -= bytesReadThisTime;
420 bufferPointer += bytesReadThisTime;
421 }
422 return SkData::NewFromMalloc(bufferStart, bytesInFile);
423}
424
425/// Decodes binary contents of baseFile and comparisonFile into
426/// diffRecord->fBaseBitmap and diffRecord->fComparisonBitmap.
427/// Returns true if that succeeds.
428static bool get_bitmaps (SkData* baseFileContents,
429 SkData* comparisonFileContents,
430 DiffRecord* diffRecord) {
431 SkMemoryStream compareStream(comparisonFileContents->data(),
432 comparisonFileContents->size());
433 SkMemoryStream baseStream(baseFileContents->data(),
434 baseFileContents->size());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000435
436 SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
437 if (NULL == codec) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000438 SkDebugf("ERROR: no codec found for basePath <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000439 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000440 return false;
441 }
442
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000443 // In debug, the DLL will automatically be unloaded when this is deleted,
444 // but that shouldn't be a problem in release mode.
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000445 SkAutoTDelete<SkImageDecoder> ad(codec);
446
447 baseStream.rewind();
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000448 if (!codec->decode(&baseStream, diffRecord->fBaseBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000449 SkBitmap::kARGB_8888_Config,
450 SkImageDecoder::kDecodePixels_Mode)) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000451 SkDebugf("ERROR: codec failed for basePath <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000452 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000453 return false;
454 }
455
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000456 diffRecord->fBaseWidth = diffRecord->fBaseBitmap->width();
457 diffRecord->fBaseHeight = diffRecord->fBaseBitmap->height();
458
459 if (!codec->decode(&compareStream, diffRecord->fComparisonBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000460 SkBitmap::kARGB_8888_Config,
461 SkImageDecoder::kDecodePixels_Mode)) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000462 SkDebugf("ERROR: codec failed for comparisonPath <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000463 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000464 return false;
465 }
466
467 return true;
468}
469
epoger@google.com5fd53852012-03-22 18:20:06 +0000470static bool get_bitmap_height_width(const SkString& path,
471 int *height, int *width) {
472 SkFILEStream stream(path.c_str());
473 if (!stream.isValid()) {
474 SkDebugf("ERROR: couldn't open file <%s>\n",
475 path.c_str());
476 return false;
477 }
478
479 SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
480 if (NULL == codec) {
481 SkDebugf("ERROR: no codec found for <%s>\n",
482 path.c_str());
483 return false;
484 }
485
486 SkAutoTDelete<SkImageDecoder> ad(codec);
487 SkBitmap bm;
488
489 stream.rewind();
490 if (!codec->decode(&stream, &bm,
491 SkBitmap::kARGB_8888_Config,
492 SkImageDecoder::kDecodePixels_Mode)) {
493 SkDebugf("ERROR: codec failed for <%s>\n",
494 path.c_str());
495 return false;
496 }
497
498 *height = bm.height();
499 *width = bm.width();
500
501 return true;
502}
503
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000504// from gm - thanks to PNG, we need to force all pixels 100% opaque
505static void force_all_opaque(const SkBitmap& bitmap) {
506 SkAutoLockPixels lock(bitmap);
507 for (int y = 0; y < bitmap.height(); y++) {
508 for (int x = 0; x < bitmap.width(); x++) {
509 *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
510 }
511 }
512}
513
514// from gm
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000515static bool write_bitmap(const SkString& path, const SkBitmap* bitmap) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000516 SkBitmap copy;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000517 bitmap->copyTo(&copy, SkBitmap::kARGB_8888_Config);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000518 force_all_opaque(copy);
519 return SkImageEncoder::EncodeFile(path.c_str(), copy,
520 SkImageEncoder::kPNG_Type, 100);
521}
522
523// from gm
524static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
525 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
526 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
527 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
528
529 return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
530}
531
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000532static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1,
533 const int threshold) {
534 int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
535 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
536 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
537 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
538
539 return ((SkAbs32(da) <= threshold) &&
540 (SkAbs32(dr) <= threshold) &&
541 (SkAbs32(dg) <= threshold) &&
542 (SkAbs32(db) <= threshold));
543}
544
545// based on gm
epoger@google.com66008522012-05-16 17:40:57 +0000546// Postcondition: when we exit this method, dr->fResult should have some value
547// other than kUnknown.
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000548static void compute_diff(DiffRecord* dr,
549 DiffMetricProc diffFunction,
550 const int colorThreshold) {
epoger@google.com25d961c2012-02-02 20:50:36 +0000551 SkAutoLockPixels alpDiff(*dr->fDifferenceBitmap);
552 SkAutoLockPixels alpWhite(*dr->fWhiteBitmap);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000553
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000554 const int w = dr->fComparisonBitmap->width();
555 const int h = dr->fComparisonBitmap->height();
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000556 int mismatchedPixels = 0;
557 int totalMismatchR = 0;
558 int totalMismatchG = 0;
559 int totalMismatchB = 0;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000560
561 if (w != dr->fBaseWidth || h != dr->fBaseHeight) {
epoger@google.com292aff62012-05-16 14:57:28 +0000562 dr->fResult = kDifferentSizes;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000563 return;
564 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000565 // Accumulate fractionally different pixels, then divide out
566 // # of pixels at the end.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000567 dr->fWeightedFraction = 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000568 for (int y = 0; y < h; y++) {
569 for (int x = 0; x < w; x++) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000570 SkPMColor c0 = *dr->fBaseBitmap->getAddr32(x, y);
571 SkPMColor c1 = *dr->fComparisonBitmap->getAddr32(x, y);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000572 SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
573 SkPMColor outputDifference = diffFunction(c0, c1);
574 uint32_t thisR = SkGetPackedR32(trueDifference);
575 uint32_t thisG = SkGetPackedG32(trueDifference);
576 uint32_t thisB = SkGetPackedB32(trueDifference);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000577 totalMismatchR += thisR;
578 totalMismatchG += thisG;
579 totalMismatchB += thisB;
580 // In HSV, value is defined as max RGB component.
581 int value = MAX3(thisR, thisG, thisB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000582 dr->fWeightedFraction += ((float) value) / 255;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000583 if (thisR > dr->fMaxMismatchR) {
584 dr->fMaxMismatchR = thisR;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000585 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000586 if (thisG > dr->fMaxMismatchG) {
587 dr->fMaxMismatchG = thisG;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000588 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000589 if (thisB > dr->fMaxMismatchB) {
590 dr->fMaxMismatchB = thisB;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000591 }
592 if (!colors_match_thresholded(c0, c1, colorThreshold)) {
593 mismatchedPixels++;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000594 *dr->fDifferenceBitmap->getAddr32(x, y) = outputDifference;
epoger@google.com25d961c2012-02-02 20:50:36 +0000595 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_WHITE;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000596 } else {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000597 *dr->fDifferenceBitmap->getAddr32(x, y) = 0;
epoger@google.com25d961c2012-02-02 20:50:36 +0000598 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_BLACK;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000599 }
600 }
601 }
epoger@google.com292aff62012-05-16 14:57:28 +0000602 if (0 == mismatchedPixels) {
603 dr->fResult = kEqualPixels;
604 return;
605 }
606 dr->fResult = kDifferentPixels;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000607 int pixelCount = w * h;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000608 dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
609 dr->fWeightedFraction /= pixelCount;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000610 dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
611 dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
612 dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000613}
614
epoger@google.com25d961c2012-02-02 20:50:36 +0000615static SkString filename_to_derived_filename (const SkString& filename,
616 const char *suffix) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000617 SkString diffName (filename);
618 const char* cstring = diffName.c_str();
619 int dotOffset = strrchr(cstring, '.') - cstring;
620 diffName.remove(dotOffset, diffName.size() - dotOffset);
epoger@google.com25d961c2012-02-02 20:50:36 +0000621 diffName.append(suffix);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000622 return diffName;
623}
624
epoger@google.com25d961c2012-02-02 20:50:36 +0000625/// Given a image filename, returns the name of the file containing the
626/// associated difference image.
627static SkString filename_to_diff_filename (const SkString& filename) {
628 return filename_to_derived_filename(filename, "-diff.png");
629}
630
631/// Given a image filename, returns the name of the file containing the
632/// "white" difference image.
633static SkString filename_to_white_filename (const SkString& filename) {
634 return filename_to_derived_filename(filename, "-white.png");
635}
636
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000637static void release_bitmaps(DiffRecord* drp) {
638 delete drp->fBaseBitmap;
639 drp->fBaseBitmap = NULL;
640 delete drp->fComparisonBitmap;
641 drp->fComparisonBitmap = NULL;
642 delete drp->fDifferenceBitmap;
643 drp->fDifferenceBitmap = NULL;
epoger@google.com25d961c2012-02-02 20:50:36 +0000644 delete drp->fWhiteBitmap;
645 drp->fWhiteBitmap = NULL;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000646}
647
tomhudson@google.com7d042802011-07-14 13:15:55 +0000648
epoger@google.coma5f406e2012-05-01 13:26:16 +0000649/// If outputDir.isEmpty(), don't write out diff files.
tomhudson@google.com7d042802011-07-14 13:15:55 +0000650static void create_and_write_diff_image(DiffRecord* drp,
651 DiffMetricProc dmp,
652 const int colorThreshold,
653 const SkString& outputDir,
654 const SkString& filename) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000655 const int w = drp->fBaseWidth;
656 const int h = drp->fBaseHeight;
657 drp->fDifferenceBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
658 drp->fDifferenceBitmap->allocPixels();
epoger@google.com25d961c2012-02-02 20:50:36 +0000659 drp->fWhiteBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
660 drp->fWhiteBitmap->allocPixels();
tomhudson@google.com7d042802011-07-14 13:15:55 +0000661
epoger@google.com66008522012-05-16 17:40:57 +0000662 SkASSERT(kUnknown == drp->fResult);
663 compute_diff(drp, dmp, colorThreshold);
664 SkASSERT(kUnknown != drp->fResult);
665
666 if ((kDifferentPixels == drp->fResult) && !outputDir.isEmpty()) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000667 SkString differencePath (outputDir);
668 differencePath.append(filename_to_diff_filename(filename));
669 write_bitmap(differencePath, drp->fDifferenceBitmap);
670 SkString whitePath (outputDir);
671 whitePath.append(filename_to_white_filename(filename));
672 write_bitmap(whitePath, drp->fWhiteBitmap);
673 }
epoger@google.com66008522012-05-16 17:40:57 +0000674
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000675 release_bitmaps(drp);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000676}
677
epoger@google.coma5f406e2012-05-01 13:26:16 +0000678/// Returns true if string contains any of these substrings.
679static bool string_contains_any_of(const SkString& string,
680 const StringArray& substrings) {
681 for (int i = 0; i < substrings.count(); i++) {
682 if (string.contains(substrings[i]->c_str())) {
683 return true;
684 }
685 }
686 return false;
687}
688
689/// Iterate over dir and get all files that:
690/// - match any of the substrings in matchSubstrings, but...
691/// - DO NOT match any of the substrings in nomatchSubstrings
692/// Returns the list of files in *files.
693static void get_file_list(const SkString& dir,
694 const StringArray& matchSubstrings,
695 const StringArray& nomatchSubstrings,
696 FileArray *files) {
epoger@google.com5fd53852012-03-22 18:20:06 +0000697 SkOSFile::Iter it(dir.c_str());
698 SkString filename;
699 while (it.next(&filename)) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000700 if (string_contains_any_of(filename, matchSubstrings) &&
701 !string_contains_any_of(filename, nomatchSubstrings)) {
702 files->push(new SkString(filename));
epoger@google.com5fd53852012-03-22 18:20:06 +0000703 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000704 }
705}
706
707static void release_file_list(FileArray *files) {
708 files->deleteAll();
709}
710
711/// Comparison routines for qsort, sort by file names.
712static int compare_file_name_metrics(SkString **lhs, SkString **rhs) {
713 return strcmp((*lhs)->c_str(), (*rhs)->c_str());
714}
715
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000716/// Creates difference images, returns the number that have a 0 metric.
epoger@google.coma5f406e2012-05-01 13:26:16 +0000717/// If outputDir.isEmpty(), don't write out diff files.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000718static void create_diff_images (DiffMetricProc dmp,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000719 const int colorThreshold,
720 RecordArray* differences,
721 const SkString& baseDir,
722 const SkString& comparisonDir,
723 const SkString& outputDir,
epoger@google.coma5f406e2012-05-01 13:26:16 +0000724 const StringArray& matchSubstrings,
725 const StringArray& nomatchSubstrings,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000726 DiffSummary* summary) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000727 SkASSERT(!baseDir.isEmpty());
728 SkASSERT(!comparisonDir.isEmpty());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000729
epoger@google.com5fd53852012-03-22 18:20:06 +0000730 FileArray baseFiles;
731 FileArray comparisonFiles;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000732
epoger@google.coma5f406e2012-05-01 13:26:16 +0000733 get_file_list(baseDir, matchSubstrings, nomatchSubstrings, &baseFiles);
734 get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings,
735 &comparisonFiles);
epoger@google.com5fd53852012-03-22 18:20:06 +0000736
epoger@google.coma5f406e2012-05-01 13:26:16 +0000737 if (!baseFiles.isEmpty()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +0000738 qsort(baseFiles.begin(), baseFiles.count(), sizeof(SkString*),
739 SkCastForQSort(compare_file_name_metrics));
epoger@google.coma5f406e2012-05-01 13:26:16 +0000740 }
741 if (!comparisonFiles.isEmpty()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +0000742 qsort(comparisonFiles.begin(), comparisonFiles.count(),
743 sizeof(SkString*), SkCastForQSort(compare_file_name_metrics));
epoger@google.coma5f406e2012-05-01 13:26:16 +0000744 }
epoger@google.com66008522012-05-16 17:40:57 +0000745
epoger@google.com5fd53852012-03-22 18:20:06 +0000746 int i = 0;
747 int j = 0;
748
749 while (i < baseFiles.count() &&
750 j < comparisonFiles.count()) {
751
tomhudson@google.com4e305982011-07-13 17:42:46 +0000752 SkString basePath (baseDir);
epoger@google.com5fd53852012-03-22 18:20:06 +0000753 basePath.append(*baseFiles[i]);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000754 SkString comparisonPath (comparisonDir);
epoger@google.com5fd53852012-03-22 18:20:06 +0000755 comparisonPath.append(*comparisonFiles[j]);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000756
epoger@google.com5fd53852012-03-22 18:20:06 +0000757 DiffRecord *drp = NULL;
758 int v = strcmp(baseFiles[i]->c_str(),
759 comparisonFiles[j]->c_str());
760
761 if (v < 0) {
762 // in baseDir, but not in comparisonDir
epoger@google.com292aff62012-05-16 14:57:28 +0000763 drp = new DiffRecord(*baseFiles[i], basePath, comparisonPath,
764 kComparisonMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000765 ++i;
766 } else if (v > 0) {
767 // in comparisonDir, but not in baseDir
epoger@google.com292aff62012-05-16 14:57:28 +0000768 drp = new DiffRecord(*comparisonFiles[j], basePath, comparisonPath,
769 kBaseMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000770 ++j;
771 } else {
epoger@google.com46256ea2012-05-22 13:45:35 +0000772 // Found the same filename in both baseDir and comparisonDir.
epoger@google.com5fd53852012-03-22 18:20:06 +0000773 drp = new DiffRecord(*baseFiles[i], basePath, comparisonPath);
epoger@google.com46256ea2012-05-22 13:45:35 +0000774 SkASSERT(kUnknown == drp->fResult);
epoger@google.com5fd53852012-03-22 18:20:06 +0000775
tomhudson@google.com8afae612012-08-14 15:03:35 +0000776 SkData* baseFileBits = NULL;
777 SkData* comparisonFileBits = NULL;
epoger@google.com46256ea2012-05-22 13:45:35 +0000778 if (NULL == (baseFileBits = read_file(basePath.c_str()))) {
779 SkDebugf("WARNING: couldn't read base file <%s>\n",
780 basePath.c_str());
781 drp->fResult = kBaseMissing;
tomhudson@google.com8afae612012-08-14 15:03:35 +0000782 } else if (NULL == (comparisonFileBits = read_file(comparisonPath.c_str()))) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000783 SkDebugf("WARNING: couldn't read comparison file <%s>\n",
784 comparisonPath.c_str());
785 drp->fResult = kComparisonMissing;
786 } else {
787 if (are_buffers_equal(baseFileBits, comparisonFileBits)) {
788 drp->fResult = kEqualBits;
789 } else if (get_bitmaps(baseFileBits, comparisonFileBits, drp)) {
790 create_and_write_diff_image(drp, dmp, colorThreshold,
791 outputDir, *baseFiles[i]);
792 } else {
793 drp->fResult = kDifferentOther;
794 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000795 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000796 if (baseFileBits) {
797 baseFileBits->unref();
798 }
799 if (comparisonFileBits) {
800 comparisonFileBits->unref();
801 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000802 ++i;
803 ++j;
804 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000805 SkASSERT(kUnknown != drp->fResult);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000806 differences->push(drp);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000807 summary->add(drp);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000808 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000809
810 for (; i < baseFiles.count(); ++i) {
811 // files only in baseDir
812 SkString basePath (baseDir);
813 basePath.append(*baseFiles[i]);
814 SkString comparisonPath;
815 DiffRecord *drp = new DiffRecord(*baseFiles[i], basePath,
epoger@google.com292aff62012-05-16 14:57:28 +0000816 comparisonPath, kComparisonMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000817 differences->push(drp);
818 summary->add(drp);
819 }
820
821 for (; j < comparisonFiles.count(); ++j) {
822 // files only in comparisonDir
823 SkString basePath;
824 SkString comparisonPath(comparisonDir);
825 comparisonPath.append(*comparisonFiles[j]);
826 DiffRecord *drp = new DiffRecord(*comparisonFiles[j], basePath,
epoger@google.com292aff62012-05-16 14:57:28 +0000827 comparisonPath, kBaseMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000828 differences->push(drp);
829 summary->add(drp);
830 }
831
832 release_file_list(&baseFiles);
833 release_file_list(&comparisonFiles);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000834}
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000835
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000836/// Make layout more consistent by scaling image to 240 height, 360 width,
837/// or natural size, whichever is smallest.
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000838static int compute_image_height (int height, int width) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000839 int retval = 240;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000840 if (height < retval) {
841 retval = height;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000842 }
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000843 float scale = (float) retval / height;
844 if (width * scale > 360) {
845 scale = (float) 360 / width;
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000846 retval = static_cast<int>(height * scale);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000847 }
848 return retval;
849}
850
epoger@google.com25d961c2012-02-02 20:50:36 +0000851static void print_table_header (SkFILEWStream* stream,
852 const int matchCount,
853 const int colorThreshold,
854 const RecordArray& differences,
855 const SkString &baseDir,
epoger@google.coma2b793c2012-05-15 14:58:53 +0000856 const SkString &comparisonDir,
857 bool doOutputDate=false) {
epoger@google.com25d961c2012-02-02 20:50:36 +0000858 stream->writeText("<table>\n");
859 stream->writeText("<tr><th>");
epoger@google.coma2b793c2012-05-15 14:58:53 +0000860 if (doOutputDate) {
861 SkTime::DateTime dt;
862 SkTime::GetDateTime(&dt);
863 stream->writeText("SkDiff run at ");
864 stream->writeDecAsText(dt.fHour);
865 stream->writeText(":");
866 if (dt.fMinute < 10) {
867 stream->writeText("0");
868 }
869 stream->writeDecAsText(dt.fMinute);
870 stream->writeText(":");
871 if (dt.fSecond < 10) {
872 stream->writeText("0");
873 }
874 stream->writeDecAsText(dt.fSecond);
875 stream->writeText("<br>");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000876 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000877 stream->writeDecAsText(matchCount);
878 stream->writeText(" of ");
879 stream->writeDecAsText(differences.count());
880 stream->writeText(" images matched ");
881 if (colorThreshold == 0) {
882 stream->writeText("exactly");
883 } else {
884 stream->writeText("within ");
885 stream->writeDecAsText(colorThreshold);
886 stream->writeText(" color units per component");
887 }
888 stream->writeText(".<br>");
epoger@google.com25d961c2012-02-02 20:50:36 +0000889 stream->writeText("</th>\n<th>");
890 stream->writeText("every different pixel shown in white");
891 stream->writeText("</th>\n<th>");
892 stream->writeText("color difference at each pixel");
epoger@google.com46a45962012-07-12 18:16:02 +0000893 stream->writeText("</th>\n<th>baseDir: ");
epoger@google.com25d961c2012-02-02 20:50:36 +0000894 stream->writeText(baseDir.c_str());
epoger@google.com46a45962012-07-12 18:16:02 +0000895 stream->writeText("</th>\n<th>comparisonDir: ");
epoger@google.com25d961c2012-02-02 20:50:36 +0000896 stream->writeText(comparisonDir.c_str());
897 stream->writeText("</th>\n");
898 stream->writeText("</tr>\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000899}
900
901static void print_pixel_count (SkFILEWStream* stream,
902 const DiffRecord& diff) {
903 stream->writeText("<br>(");
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000904 stream->writeDecAsText(static_cast<int>(diff.fFractionDifference *
905 diff.fBaseWidth *
906 diff.fBaseHeight));
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000907 stream->writeText(" pixels)");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000908/*
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000909 stream->writeDecAsText(diff.fWeightedFraction *
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000910 diff.fBaseWidth *
911 diff.fBaseHeight);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000912 stream->writeText(" weighted pixels)");
913*/
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000914}
915
916static void print_label_cell (SkFILEWStream* stream,
917 const DiffRecord& diff) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000918 char metricBuf [20];
919
920 stream->writeText("<td><b>");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000921 stream->writeText(diff.fFilename.c_str());
epoger@google.com46256ea2012-05-22 13:45:35 +0000922 stream->writeText("</b><br>");
epoger@google.com292aff62012-05-16 14:57:28 +0000923 switch (diff.fResult) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000924 case kEqualBits:
925 SkDEBUGFAIL("should not encounter DiffRecord with kEqualBits here");
926 return;
927 case kEqualPixels:
928 SkDEBUGFAIL("should not encounter DiffRecord with kEqualPixels here");
epoger@google.com5fd53852012-03-22 18:20:06 +0000929 return;
epoger@google.com292aff62012-05-16 14:57:28 +0000930 case kDifferentSizes:
epoger@google.com46256ea2012-05-22 13:45:35 +0000931 stream->writeText("Image sizes differ</td>");
932 return;
933 case kDifferentPixels:
934 sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
935 stream->writeText(metricBuf);
936 stream->writeText(" of pixels differ");
937 stream->writeText("\n (");
938 sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
939 stream->writeText(metricBuf);
940 stream->writeText(" weighted)");
941 // Write the actual number of pixels that differ if it's < 1%
942 if (diff.fFractionDifference < 0.01) {
943 print_pixel_count(stream, diff);
944 }
945 stream->writeText("<br>Average color mismatch ");
946 stream->writeDecAsText(static_cast<int>(MAX3(diff.fAverageMismatchR,
947 diff.fAverageMismatchG,
948 diff.fAverageMismatchB)));
949 stream->writeText("<br>Max color mismatch ");
950 stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
951 diff.fMaxMismatchG,
952 diff.fMaxMismatchB));
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000953 stream->writeText("</td>");
epoger@google.com46256ea2012-05-22 13:45:35 +0000954 break;
955 case kDifferentOther:
956 stream->writeText("Files differ; unable to parse one or both files</td>");
957 return;
958 case kBaseMissing:
959 stream->writeText("Missing from baseDir</td>");
960 return;
961 case kComparisonMissing:
962 stream->writeText("Missing from comparisonDir</td>");
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000963 return;
epoger@google.com292aff62012-05-16 14:57:28 +0000964 default:
epoger@google.com46256ea2012-05-22 13:45:35 +0000965 SkDEBUGFAIL("encountered DiffRecord with unknown result type");
966 return;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000967 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000968}
969
970static void print_image_cell (SkFILEWStream* stream,
tomhudson@google.com4e305982011-07-13 17:42:46 +0000971 const SkString& path,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000972 int height) {
973 stream->writeText("<td><a href=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000974 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000975 stream->writeText("\"><img src=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000976 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000977 stream->writeText("\" height=\"");
978 stream->writeDecAsText(height);
979 stream->writeText("px\"></a></td>");
980}
981
caryclark@google.com3dd45912012-06-06 12:11:10 +0000982#if 0 // UNUSED
epoger@google.com01f78702012-04-12 16:32:04 +0000983static void print_text_cell (SkFILEWStream* stream, const char* text) {
984 stream->writeText("<td align=center>");
985 if (NULL != text) {
986 stream->writeText(text);
987 }
988 stream->writeText("</td>");
989}
caryclark@google.com3dd45912012-06-06 12:11:10 +0000990#endif
epoger@google.com01f78702012-04-12 16:32:04 +0000991
epoger@google.com5fd53852012-03-22 18:20:06 +0000992static void print_diff_with_missing_file(SkFILEWStream* stream,
993 DiffRecord& diff,
994 const SkString& relativePath) {
995 stream->writeText("<tr>\n");
996 print_label_cell(stream, diff);
997 stream->writeText("<td>N/A</td>");
998 stream->writeText("<td>N/A</td>");
epoger@google.com292aff62012-05-16 14:57:28 +0000999 if (kBaseMissing != diff.fResult) {
epoger@google.com5fd53852012-03-22 18:20:06 +00001000 int h, w;
1001 if (!get_bitmap_height_width(diff.fBasePath, &h, &w)) {
1002 stream->writeText("<td>N/A</td>");
1003 } else {
1004 int height = compute_image_height(h, w);
1005 if (!diff.fBasePath.startsWith(PATH_DIV_STR)) {
1006 diff.fBasePath.prepend(relativePath);
1007 }
1008 print_image_cell(stream, diff.fBasePath, height);
1009 }
1010 } else {
1011 stream->writeText("<td>N/A</td>");
1012 }
epoger@google.com292aff62012-05-16 14:57:28 +00001013 if (kComparisonMissing != diff.fResult) {
epoger@google.com5fd53852012-03-22 18:20:06 +00001014 int h, w;
1015 if (!get_bitmap_height_width(diff.fComparisonPath, &h, &w)) {
1016 stream->writeText("<td>N/A</td>");
1017 } else {
1018 int height = compute_image_height(h, w);
1019 if (!diff.fComparisonPath.startsWith(PATH_DIV_STR)) {
1020 diff.fComparisonPath.prepend(relativePath);
1021 }
1022 print_image_cell(stream, diff.fComparisonPath, height);
1023 }
1024 } else {
1025 stream->writeText("<td>N/A</td>");
1026 }
1027 stream->writeText("</tr>\n");
1028 stream->flush();
1029}
1030
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001031static void print_diff_page (const int matchCount,
1032 const int colorThreshold,
1033 const RecordArray& differences,
1034 const SkString& baseDir,
1035 const SkString& comparisonDir,
1036 const SkString& outputDir) {
1037
epoger@google.coma5f406e2012-05-01 13:26:16 +00001038 SkASSERT(!baseDir.isEmpty());
1039 SkASSERT(!comparisonDir.isEmpty());
1040 SkASSERT(!outputDir.isEmpty());
1041
tomhudson@google.com5b325292011-05-24 19:41:13 +00001042 SkString outputPath (outputDir);
1043 outputPath.append("index.html");
1044 //SkFILEWStream outputStream ("index.html");
1045 SkFILEWStream outputStream (outputPath.c_str());
1046
tomhudson@google.com4e305982011-07-13 17:42:46 +00001047 // Need to convert paths from relative-to-cwd to relative-to-outputDir
tomhudson@google.com5b325292011-05-24 19:41:13 +00001048 // FIXME this doesn't work if there are '..' inside the outputDir
bsalomon@google.comf1478f82012-07-30 19:16:02 +00001049
epoger@google.com21ecba82012-08-08 13:47:39 +00001050 bool isPathAbsolute = false;
1051 // On Windows or Linux, a path starting with PATH_DIV_CHAR is absolute.
1052 if (outputDir.size() > 0 && PATH_DIV_CHAR == outputDir[0]) {
1053 isPathAbsolute = true;
1054 }
bsalomon@google.comf1478f82012-07-30 19:16:02 +00001055#ifdef SK_BUILD_FOR_WIN32
epoger@google.com21ecba82012-08-08 13:47:39 +00001056 // On Windows, absolute paths can also start with "x:", where x is any
1057 // drive letter.
1058 if (outputDir.size() > 1 && ':' == outputDir[1]) {
1059 isPathAbsolute = true;
1060 }
bsalomon@google.comf1478f82012-07-30 19:16:02 +00001061#endif
epoger@google.com21ecba82012-08-08 13:47:39 +00001062
tomhudson@google.com5b325292011-05-24 19:41:13 +00001063 SkString relativePath;
epoger@google.com21ecba82012-08-08 13:47:39 +00001064 if (!isPathAbsolute) {
bsalomon@google.comf1478f82012-07-30 19:16:02 +00001065 unsigned int ui;
1066 for (ui = 0; ui < outputDir.size(); ui++) {
1067 if (outputDir[ui] == PATH_DIV_CHAR) {
1068 relativePath.append(".." PATH_DIV_STR);
1069 }
tomhudson@google.com5b325292011-05-24 19:41:13 +00001070 }
1071 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001072
1073 outputStream.writeText("<html>\n<body>\n");
epoger@google.com25d961c2012-02-02 20:50:36 +00001074 print_table_header(&outputStream, matchCount, colorThreshold, differences,
1075 baseDir, comparisonDir);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001076 int i;
1077 for (i = 0; i < differences.count(); i++) {
1078 DiffRecord* diff = differences[i];
epoger@google.com5fd53852012-03-22 18:20:06 +00001079
epoger@google.com292aff62012-05-16 14:57:28 +00001080 switch (diff->fResult) {
epoger@google.com46256ea2012-05-22 13:45:35 +00001081 // Cases in which there is no diff to report.
1082 case kEqualBits:
epoger@google.com292aff62012-05-16 14:57:28 +00001083 case kEqualPixels:
1084 continue;
epoger@google.com46256ea2012-05-22 13:45:35 +00001085 // Cases in which we want a detailed pixel diff.
1086 case kDifferentPixels:
1087 break;
1088 // Cases in which the files differed, but we can't display the diff.
1089 case kDifferentSizes:
1090 case kDifferentOther:
epoger@google.com292aff62012-05-16 14:57:28 +00001091 case kBaseMissing:
epoger@google.com292aff62012-05-16 14:57:28 +00001092 case kComparisonMissing:
epoger@google.com5fd53852012-03-22 18:20:06 +00001093 print_diff_with_missing_file(&outputStream, *diff, relativePath);
1094 continue;
epoger@google.com292aff62012-05-16 14:57:28 +00001095 default:
epoger@google.com46256ea2012-05-22 13:45:35 +00001096 SkDEBUGFAIL("encountered DiffRecord with unknown result type");
1097 continue;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001098 }
epoger@google.com5fd53852012-03-22 18:20:06 +00001099
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001100 if (!diff->fBasePath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +00001101 diff->fBasePath.prepend(relativePath);
1102 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001103 if (!diff->fComparisonPath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +00001104 diff->fComparisonPath.prepend(relativePath);
1105 }
epoger@google.com5fd53852012-03-22 18:20:06 +00001106
tomhudson@google.com9b540ce2011-08-02 14:10:04 +00001107 int height = compute_image_height(diff->fBaseHeight, diff->fBaseWidth);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001108 outputStream.writeText("<tr>\n");
1109 print_label_cell(&outputStream, *diff);
epoger@google.com46256ea2012-05-22 13:45:35 +00001110 print_image_cell(&outputStream,
1111 filename_to_white_filename(diff->fFilename), height);
1112 print_image_cell(&outputStream,
1113 filename_to_diff_filename(diff->fFilename), height);
epoger@google.com25d961c2012-02-02 20:50:36 +00001114 print_image_cell(&outputStream, diff->fBasePath, height);
tomhudson@google.com4e305982011-07-13 17:42:46 +00001115 print_image_cell(&outputStream, diff->fComparisonPath, height);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001116 outputStream.writeText("</tr>\n");
1117 outputStream.flush();
1118 }
1119 outputStream.writeText("</table>\n");
1120 outputStream.writeText("</body>\n</html>\n");
1121 outputStream.flush();
1122}
1123
1124static void usage (char * argv0) {
1125 SkDebugf("Skia baseline image diff tool\n");
epoger@google.coma5f406e2012-05-01 13:26:16 +00001126 SkDebugf("\n"
1127"Usage: \n"
1128" %s <baseDir> <comparisonDir> [outputDir] \n"
epoger@google.coma611c3e2012-05-18 20:10:06 +00001129, argv0, argv0);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001130 SkDebugf(
epoger@google.com46a45962012-07-12 18:16:02 +00001131"\nArguments:"
epoger@google.comdfbf24e2012-07-13 21:22:02 +00001132"\n --failonresult <result>: After comparing all file pairs, exit with nonzero"
1133"\n return code (number of file pairs yielding this"
1134"\n result) if any file pairs yielded this result."
1135"\n This flag may be repeated, in which case the"
1136"\n return code will be the number of fail pairs"
1137"\n yielding ANY of these results."
epoger@google.com46a45962012-07-12 18:16:02 +00001138"\n --help: display this info"
epoger@google.com46a45962012-07-12 18:16:02 +00001139"\n --listfilenames: list all filenames for each result type in stdout"
epoger@google.comdfbf24e2012-07-13 21:22:02 +00001140"\n --match <substring>: compare files whose filenames contain this substring;"
1141"\n if unspecified, compare ALL files."
1142"\n this flag may be repeated."
epoger@google.com46a45962012-07-12 18:16:02 +00001143"\n --nodiffs: don't write out image diffs or index.html, just generate"
1144"\n report on stdout"
epoger@google.comdfbf24e2012-07-13 21:22:02 +00001145"\n --nomatch <substring>: regardless of --match, DO NOT compare files whose"
1146"\n filenames contain this substring."
1147"\n this flag may be repeated."
epoger@google.com46a45962012-07-12 18:16:02 +00001148"\n --noprintdirs: do not print the directories used."
1149"\n --sortbymaxmismatch: sort by worst color channel mismatch;"
1150"\n break ties with -sortbymismatch"
1151"\n --sortbymismatch: sort by average color channel mismatch"
1152"\n --threshold <n>: only report differences > n (per color channel) [default 0]"
1153"\n --weighted: sort by # pixels different weighted by color difference"
1154"\n"
1155"\n baseDir: directory to read baseline images from."
1156"\n comparisonDir: directory to read comparison images from"
1157"\n outputDir: directory to write difference images and index.html to;"
1158"\n defaults to comparisonDir"
1159"\n"
1160"\nIf no sort is specified, it will sort by fraction of pixels mismatching."
1161"\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001162}
1163
epoger@google.com70044cc2012-07-12 18:37:55 +00001164const int kNoError = 0;
1165const int kGenericError = -1;
epoger@google.com46a45962012-07-12 18:16:02 +00001166
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001167int main (int argc, char ** argv) {
1168 DiffMetricProc diffProc = compute_diff_pmcolor;
epoger@google.com28060e72012-06-28 16:47:34 +00001169 int (*sortProc)(const void*, const void*) = compare<CompareDiffMetrics>;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001170
1171 // Maximum error tolerated in any one color channel in any one pixel before
1172 // a difference is reported.
1173 int colorThreshold = 0;
1174 SkString baseDir;
1175 SkString comparisonDir;
1176 SkString outputDir;
epoger@google.comdfbf24e2012-07-13 21:22:02 +00001177
epoger@google.coma5f406e2012-05-01 13:26:16 +00001178 StringArray matchSubstrings;
1179 StringArray nomatchSubstrings;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001180
epoger@google.coma5f406e2012-05-01 13:26:16 +00001181 bool generateDiffs = true;
epoger@google.com46a45962012-07-12 18:16:02 +00001182 bool listFilenames = false;
keyar@chromium.orga6318192012-07-09 21:01:50 +00001183 bool printDirs = true;
tomhudson@google.com7d042802011-07-14 13:15:55 +00001184
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001185 RecordArray differences;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001186 DiffSummary summary;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001187
epoger@google.com3af4ff42012-07-19 17:35:04 +00001188 bool failOnResultType[kNumResultTypes];
1189 for (int i = 0; i < kNumResultTypes; i++) {
1190 failOnResultType[i] = false;
1191 }
1192
epoger@google.coma5f406e2012-05-01 13:26:16 +00001193 int i;
1194 int numUnflaggedArguments = 0;
1195 for (i = 1; i < argc; i++) {
epoger@google.comdfbf24e2012-07-13 21:22:02 +00001196 if (!strcmp(argv[i], "--failonresult")) {
1197 Result type = getResultByName(argv[++i]);
epoger@google.com3af4ff42012-07-19 17:35:04 +00001198 failOnResultType[type] = true;
epoger@google.coma5f406e2012-05-01 13:26:16 +00001199 continue;
1200 }
epoger@google.com46a45962012-07-12 18:16:02 +00001201 if (!strcmp(argv[i], "--help")) {
1202 usage(argv[0]);
epoger@google.com70044cc2012-07-12 18:37:55 +00001203 return kNoError;
epoger@google.com46a45962012-07-12 18:16:02 +00001204 }
1205 if (!strcmp(argv[i], "--listfilenames")) {
1206 listFilenames = true;
epoger@google.coma5f406e2012-05-01 13:26:16 +00001207 continue;
1208 }
1209 if (!strcmp(argv[i], "--match")) {
1210 matchSubstrings.push(new SkString(argv[++i]));
1211 continue;
1212 }
epoger@google.com46a45962012-07-12 18:16:02 +00001213 if (!strcmp(argv[i], "--nodiffs")) {
1214 generateDiffs = false;
1215 continue;
1216 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001217 if (!strcmp(argv[i], "--nomatch")) {
1218 nomatchSubstrings.push(new SkString(argv[++i]));
1219 continue;
1220 }
epoger@google.com46a45962012-07-12 18:16:02 +00001221 if (!strcmp(argv[i], "--noprintdirs")) {
1222 printDirs = false;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001223 continue;
1224 }
tomhudson@google.com7d042802011-07-14 13:15:55 +00001225 if (!strcmp(argv[i], "--sortbymaxmismatch")) {
epoger@google.com28060e72012-06-28 16:47:34 +00001226 sortProc = compare<CompareDiffMaxMismatches>;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001227 continue;
1228 }
epoger@google.com46a45962012-07-12 18:16:02 +00001229 if (!strcmp(argv[i], "--sortbymismatch")) {
1230 sortProc = compare<CompareDiffMeanMismatches>;
tomhudson@google.com5b325292011-05-24 19:41:13 +00001231 continue;
1232 }
epoger@google.com46a45962012-07-12 18:16:02 +00001233 if (!strcmp(argv[i], "--threshold")) {
1234 colorThreshold = atoi(argv[++i]);
1235 continue;
1236 }
1237 if (!strcmp(argv[i], "--weighted")) {
1238 sortProc = compare<CompareDiffWeighted>;
keyar@chromium.orga6318192012-07-09 21:01:50 +00001239 continue;
1240 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001241 if (argv[i][0] != '-') {
epoger@google.coma5f406e2012-05-01 13:26:16 +00001242 switch (numUnflaggedArguments++) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001243 case 0:
1244 baseDir.set(argv[i]);
1245 continue;
1246 case 1:
1247 comparisonDir.set(argv[i]);
1248 continue;
1249 case 2:
1250 outputDir.set(argv[i]);
1251 continue;
1252 default:
epoger@google.coma5f406e2012-05-01 13:26:16 +00001253 SkDebugf("extra unflagged argument <%s>\n", argv[i]);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001254 usage(argv[0]);
epoger@google.com70044cc2012-07-12 18:37:55 +00001255 return kGenericError;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001256 }
1257 }
1258
1259 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
1260 usage(argv[0]);
epoger@google.com70044cc2012-07-12 18:37:55 +00001261 return kGenericError;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001262 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001263
epoger@google.coma5f406e2012-05-01 13:26:16 +00001264 if (numUnflaggedArguments == 2) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001265 outputDir = comparisonDir;
epoger@google.coma5f406e2012-05-01 13:26:16 +00001266 } else if (numUnflaggedArguments != 3) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001267 usage(argv[0]);
epoger@google.com70044cc2012-07-12 18:37:55 +00001268 return kGenericError;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001269 }
1270
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001271 if (!baseDir.endsWith(PATH_DIV_STR)) {
1272 baseDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001273 }
keyar@chromium.orga6318192012-07-09 21:01:50 +00001274 if (printDirs) {
1275 printf("baseDir is [%s]\n", baseDir.c_str());
1276 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001277
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001278 if (!comparisonDir.endsWith(PATH_DIV_STR)) {
1279 comparisonDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001280 }
keyar@chromium.orga6318192012-07-09 21:01:50 +00001281 if (printDirs) {
1282 printf("comparisonDir is [%s]\n", comparisonDir.c_str());
1283 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001284
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001285 if (!outputDir.endsWith(PATH_DIV_STR)) {
1286 outputDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001287 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001288 if (generateDiffs) {
keyar@chromium.orga6318192012-07-09 21:01:50 +00001289 if (printDirs) {
1290 printf("writing diffs to outputDir is [%s]\n", outputDir.c_str());
1291 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001292 } else {
keyar@chromium.orga6318192012-07-09 21:01:50 +00001293 if (printDirs) {
1294 printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str());
1295 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001296 outputDir.set("");
1297 }
1298
epoger@google.comda4af242012-06-25 18:45:50 +00001299 // If no matchSubstrings were specified, match ALL strings
1300 // (except for whatever nomatchSubstrings were specified, if any).
epoger@google.coma5f406e2012-05-01 13:26:16 +00001301 if (matchSubstrings.isEmpty()) {
1302 matchSubstrings.push(new SkString(""));
1303 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001304
epoger@google.coma611c3e2012-05-18 20:10:06 +00001305 create_diff_images(diffProc, colorThreshold, &differences,
1306 baseDir, comparisonDir, outputDir,
1307 matchSubstrings, nomatchSubstrings, &summary);
epoger@google.com3af4ff42012-07-19 17:35:04 +00001308 summary.print(listFilenames, failOnResultType);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001309
1310 if (differences.count()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +00001311 qsort(differences.begin(), differences.count(),
1312 sizeof(DiffRecord*), sortProc);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001313 }
epoger@google.com66008522012-05-16 17:40:57 +00001314
epoger@google.coma5f406e2012-05-01 13:26:16 +00001315 if (generateDiffs) {
1316 print_diff_page(summary.fNumMatches, colorThreshold, differences,
1317 baseDir, comparisonDir, outputDir);
1318 }
epoger@google.com76222c02012-05-31 15:12:09 +00001319
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001320 for (i = 0; i < differences.count(); i++) {
1321 delete differences[i];
1322 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001323 matchSubstrings.deleteAll();
1324 nomatchSubstrings.deleteAll();
epoger@google.combe6188d2012-05-31 15:13:45 +00001325
epoger@google.comdfbf24e2012-07-13 21:22:02 +00001326 int num_failing_results = 0;
epoger@google.com3af4ff42012-07-19 17:35:04 +00001327 for (int i = 0; i < kNumResultTypes; i++) {
1328 if (failOnResultType[i]) {
1329 num_failing_results += summary.fResultsOfType[i].count();
1330 }
epoger@google.com46a45962012-07-12 18:16:02 +00001331 }
epoger@google.com28659882012-07-16 18:01:06 +00001332
1333 // On Linux (and maybe other platforms too), any results outside of the
1334 // range [0...255] are wrapped (mod 256). Do the conversion ourselves, to
1335 // make sure that we only return 0 when there were no failures.
1336 return (num_failing_results > 255) ? 255 : num_failing_results;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001337}