blob: 48e3aedfa49852de86fa283036a56d8830276278 [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.
30 */
31
bsalomon@google.com1a315fe2011-09-23 14:56:37 +000032#if SK_BUILD_FOR_WIN32
33 #define PATH_DIV_STR "\\"
34 #define PATH_DIV_CHAR '\\'
35#else
36 #define PATH_DIV_STR "/"
37 #define PATH_DIV_CHAR '/'
38#endif
39
epoger@google.com292aff62012-05-16 14:57:28 +000040// Result of comparison for each pair of files.
epoger@google.com292aff62012-05-16 14:57:28 +000041enum Result {
42 kEqualBits, // both files in the pair contain exactly the same bits
43 kEqualPixels, // not bitwise equal, but their pixels are exactly the same
44 kDifferentSizes, // both are images we can parse, but of different sizes
45 kDifferentPixels,// both are images we can parse, but with different pixels
46 kDifferentOther, // files have different bits but are not parsable images
47 kBaseMissing, // missing from baseDir
48 kComparisonMissing,// missing from comparisonDir
49 kUnknown
50};
51
tomhudson@google.com4b33d282011-04-27 15:39:30 +000052struct DiffRecord {
tomhudson@google.com4e305982011-07-13 17:42:46 +000053 DiffRecord (const SkString filename,
54 const SkString basePath,
epoger@google.com5fd53852012-03-22 18:20:06 +000055 const SkString comparisonPath,
epoger@google.com292aff62012-05-16 14:57:28 +000056 const Result result = kUnknown)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000057 : fFilename (filename)
tomhudson@google.com4e305982011-07-13 17:42:46 +000058 , fBasePath (basePath)
59 , fComparisonPath (comparisonPath)
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000060 , fBaseBitmap (new SkBitmap ())
61 , fComparisonBitmap (new SkBitmap ())
62 , fDifferenceBitmap (new SkBitmap ())
epoger@google.com25d961c2012-02-02 20:50:36 +000063 , fWhiteBitmap (new SkBitmap ())
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000064 , fBaseHeight (0)
65 , fBaseWidth (0)
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000066 , fFractionDifference (0)
67 , fWeightedFraction (0)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000068 , fAverageMismatchR (0)
69 , fAverageMismatchG (0)
70 , fAverageMismatchB (0)
71 , fMaxMismatchR (0)
72 , fMaxMismatchG (0)
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +000073 , fMaxMismatchB (0)
epoger@google.com292aff62012-05-16 14:57:28 +000074 , fResult(result) {
tomhudson@google.com4e305982011-07-13 17:42:46 +000075 };
tomhudson@google.com4b33d282011-04-27 15:39:30 +000076
77 SkString fFilename;
tomhudson@google.com4e305982011-07-13 17:42:46 +000078 SkString fBasePath;
79 SkString fComparisonPath;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000080
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000081 SkBitmap* fBaseBitmap;
82 SkBitmap* fComparisonBitmap;
83 SkBitmap* fDifferenceBitmap;
epoger@google.com25d961c2012-02-02 20:50:36 +000084 SkBitmap* fWhiteBitmap;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000085
86 int fBaseHeight;
87 int fBaseWidth;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000088
89 /// Arbitrary floating-point metric to be used to sort images from most
90 /// to least different from baseline; values of 0 will be omitted from the
91 /// summary webpage.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000092 float fFractionDifference;
93 float fWeightedFraction;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000094
95 float fAverageMismatchR;
96 float fAverageMismatchG;
97 float fAverageMismatchB;
98
99 uint32_t fMaxMismatchR;
100 uint32_t fMaxMismatchG;
101 uint32_t fMaxMismatchB;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000102
epoger@google.com292aff62012-05-16 14:57:28 +0000103 /// Which category of diff result.
104 Result fResult;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000105};
106
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000107#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
108#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
109
epoger@google.com25d961c2012-02-02 20:50:36 +0000110const SkPMColor PMCOLOR_WHITE = SkPreMultiplyColor(SK_ColorWHITE);
111const SkPMColor PMCOLOR_BLACK = SkPreMultiplyColor(SK_ColorBLACK);
112
epoger@google.coma5f406e2012-05-01 13:26:16 +0000113typedef SkTDArray<SkString*> StringArray;
114typedef StringArray FileArray;
epoger@google.com5fd53852012-03-22 18:20:06 +0000115
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000116struct DiffSummary {
117 DiffSummary ()
118 : fNumMatches (0)
119 , fNumMismatches (0)
120 , fMaxMismatchV (0)
121 , fMaxMismatchPercent (0) { };
122
epoger@google.com5fd53852012-03-22 18:20:06 +0000123 ~DiffSummary() {
124 fBaseMissing.deleteAll();
125 fComparisonMissing.deleteAll();
126 }
127
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000128 uint32_t fNumMatches;
129 uint32_t fNumMismatches;
130 uint32_t fMaxMismatchV;
131 float fMaxMismatchPercent;
132
epoger@google.com5fd53852012-03-22 18:20:06 +0000133 FileArray fBaseMissing;
134 FileArray fComparisonMissing;
135
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000136 void print () {
epoger@google.com5fd53852012-03-22 18:20:06 +0000137 int n = fBaseMissing.count();
138 if (n > 0) {
139 printf("Missing in baseDir:\n");
140 for (int i = 0; i < n; ++i) {
141 printf("\t%s\n", fBaseMissing[i]->c_str());
142 }
143 }
144 n = fComparisonMissing.count();
145 if (n > 0) {
146 printf("Missing in comparisonDir:\n");
147 for (int i = 0; i < n; ++i) {
148 printf("\t%s\n", fComparisonMissing[i]->c_str());
149 }
150 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000151 printf("%d of %d images matched.\n", fNumMatches,
152 fNumMatches + fNumMismatches);
153 if (fNumMismatches > 0) {
154 printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
155 printf("Largest area mismatch was %.2f%% of pixels\n",
156 fMaxMismatchPercent);
157 }
158
159 }
160
161 void add (DiffRecord* drp) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000162 uint32_t mismatchValue;
epoger@google.com292aff62012-05-16 14:57:28 +0000163
164 switch (drp->fResult) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000165 case kEqualBits:
166 fNumMatches++;
167 break;
epoger@google.com292aff62012-05-16 14:57:28 +0000168 case kEqualPixels:
169 fNumMatches++;
170 break;
epoger@google.com46256ea2012-05-22 13:45:35 +0000171 case kDifferentSizes:
epoger@google.com5fd53852012-03-22 18:20:06 +0000172 fNumMismatches++;
epoger@google.com46256ea2012-05-22 13:45:35 +0000173 drp->fFractionDifference = 2.0;// sort as if 200% of pixels differed
epoger@google.com292aff62012-05-16 14:57:28 +0000174 break;
epoger@google.com46256ea2012-05-22 13:45:35 +0000175 case kDifferentPixels:
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000176 fNumMismatches++;
177 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
178 fMaxMismatchPercent = drp->fFractionDifference * 100;
179 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000180 mismatchValue = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
181 drp->fMaxMismatchB);
182 if (mismatchValue > fMaxMismatchV) {
183 fMaxMismatchV = mismatchValue;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000184 }
epoger@google.com292aff62012-05-16 14:57:28 +0000185 break;
epoger@google.com46256ea2012-05-22 13:45:35 +0000186 case kDifferentOther:
187 fNumMismatches++;
188 drp->fFractionDifference = 3.0;// sort as if 300% of pixels differed
189 break;
190 case kBaseMissing:
191 fNumMismatches++;
192 fBaseMissing.push(new SkString(drp->fFilename));
193 break;
194 case kComparisonMissing:
195 fNumMismatches++;
196 fComparisonMissing.push(new SkString(drp->fFilename));
197 break;
198 case kUnknown:
199 SkDEBUGFAIL("adding uncategorized DiffRecord");
200 break;
201 default:
202 SkDEBUGFAIL("adding DiffRecord with unhandled fResult value");
203 break;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000204 }
205 }
206};
207
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000208typedef SkTDArray<DiffRecord*> RecordArray;
209
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000210/// Comparison routine for qsort; sorts by fFractionDifference
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000211/// from largest to smallest.
212static int compare_diff_metrics (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000213 if ((*lhs)->fFractionDifference < (*rhs)->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000214 return 1;
215 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000216 if ((*rhs)->fFractionDifference < (*lhs)->fFractionDifference) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000217 return -1;
218 }
219 return 0;
220}
221
222static int compare_diff_weighted (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000223 if ((*lhs)->fWeightedFraction < (*rhs)->fWeightedFraction) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000224 return 1;
225 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000226 if ((*lhs)->fWeightedFraction > (*rhs)->fWeightedFraction) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000227 return -1;
228 }
229 return 0;
230}
231
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000232/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
233/// from largest to smallest.
234static int compare_diff_mean_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
235 float leftValue = MAX3((*lhs)->fAverageMismatchR,
236 (*lhs)->fAverageMismatchG,
237 (*lhs)->fAverageMismatchB);
238 float rightValue = MAX3((*rhs)->fAverageMismatchR,
239 (*rhs)->fAverageMismatchG,
240 (*rhs)->fAverageMismatchB);
241 if (leftValue < rightValue) {
242 return 1;
243 }
244 if (rightValue < leftValue) {
245 return -1;
246 }
247 return 0;
248}
249
250/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB})
251/// from largest to smallest.
252static int compare_diff_max_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000253 uint32_t leftValue = MAX3((*lhs)->fMaxMismatchR,
254 (*lhs)->fMaxMismatchG,
255 (*lhs)->fMaxMismatchB);
256 uint32_t rightValue = MAX3((*rhs)->fMaxMismatchR,
257 (*rhs)->fMaxMismatchG,
258 (*rhs)->fMaxMismatchB);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000259 if (leftValue < rightValue) {
260 return 1;
261 }
262 if (rightValue < leftValue) {
263 return -1;
264 }
265 return compare_diff_mean_mismatches(lhs, rhs);
266}
267
268
269
270/// Parameterized routine to compute the color of a pixel in a difference image.
271typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
272
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000273static void expand_and_copy (int width, int height, SkBitmap** dest) {
274 SkBitmap* temp = new SkBitmap ();
275 temp->reset();
276 temp->setConfig((*dest)->config(), width, height);
277 temp->allocPixels();
278 (*dest)->copyPixelsTo(temp->getPixels(), temp->getSize(),
279 temp->rowBytes());
280 *dest = temp;
281}
282
epoger@google.com46256ea2012-05-22 13:45:35 +0000283/// Returns true if the two buffers passed in are both non-NULL, and include
284/// exactly the same byte values (and identical lengths).
285static bool are_buffers_equal(SkData* skdata1, SkData* skdata2) {
286 if ((NULL == skdata1) || (NULL == skdata2)) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000287 return false;
288 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000289 if (skdata1->size() != skdata2->size()) {
290 return false;
291 }
292 return (0 == memcmp(skdata1->data(), skdata2->data(), skdata1->size()));
293}
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000294
epoger@google.com46256ea2012-05-22 13:45:35 +0000295/// Reads the file at the given path and returns its complete contents as an
296/// SkData object (or returns NULL on error).
297static SkData* read_file(const char* file_path) {
298 SkFILEStream fileStream(file_path);
299 if (!fileStream.isValid()) {
300 SkDebugf("WARNING: could not open file <%s> for reading\n", file_path);
301 return NULL;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000302 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000303 size_t bytesInFile = fileStream.getLength();
304 size_t bytesLeftToRead = bytesInFile;
305
306 void* bufferStart = sk_malloc_throw(bytesInFile);
307 char* bufferPointer = (char*)bufferStart;
308 while (bytesLeftToRead > 0) {
309 size_t bytesReadThisTime = fileStream.read(
310 bufferPointer, bytesLeftToRead);
311 if (0 == bytesReadThisTime) {
312 SkDebugf("WARNING: error reading from <%s>\n", file_path);
313 sk_free(bufferStart);
314 return NULL;
315 }
316 bytesLeftToRead -= bytesReadThisTime;
317 bufferPointer += bytesReadThisTime;
318 }
319 return SkData::NewFromMalloc(bufferStart, bytesInFile);
320}
321
322/// Decodes binary contents of baseFile and comparisonFile into
323/// diffRecord->fBaseBitmap and diffRecord->fComparisonBitmap.
324/// Returns true if that succeeds.
325static bool get_bitmaps (SkData* baseFileContents,
326 SkData* comparisonFileContents,
327 DiffRecord* diffRecord) {
328 SkMemoryStream compareStream(comparisonFileContents->data(),
329 comparisonFileContents->size());
330 SkMemoryStream baseStream(baseFileContents->data(),
331 baseFileContents->size());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000332
333 SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
334 if (NULL == codec) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000335 SkDebugf("ERROR: no codec found for basePath <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000336 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000337 return false;
338 }
339
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000340 // In debug, the DLL will automatically be unloaded when this is deleted,
341 // but that shouldn't be a problem in release mode.
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000342 SkAutoTDelete<SkImageDecoder> ad(codec);
343
344 baseStream.rewind();
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000345 if (!codec->decode(&baseStream, diffRecord->fBaseBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000346 SkBitmap::kARGB_8888_Config,
347 SkImageDecoder::kDecodePixels_Mode)) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000348 SkDebugf("ERROR: codec failed for basePath <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000349 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000350 return false;
351 }
352
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000353 diffRecord->fBaseWidth = diffRecord->fBaseBitmap->width();
354 diffRecord->fBaseHeight = diffRecord->fBaseBitmap->height();
355
356 if (!codec->decode(&compareStream, diffRecord->fComparisonBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000357 SkBitmap::kARGB_8888_Config,
358 SkImageDecoder::kDecodePixels_Mode)) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000359 SkDebugf("ERROR: codec failed for comparisonPath <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000360 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000361 return false;
362 }
363
364 return true;
365}
366
epoger@google.com5fd53852012-03-22 18:20:06 +0000367static bool get_bitmap_height_width(const SkString& path,
368 int *height, int *width) {
369 SkFILEStream stream(path.c_str());
370 if (!stream.isValid()) {
371 SkDebugf("ERROR: couldn't open file <%s>\n",
372 path.c_str());
373 return false;
374 }
375
376 SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
377 if (NULL == codec) {
378 SkDebugf("ERROR: no codec found for <%s>\n",
379 path.c_str());
380 return false;
381 }
382
383 SkAutoTDelete<SkImageDecoder> ad(codec);
384 SkBitmap bm;
385
386 stream.rewind();
387 if (!codec->decode(&stream, &bm,
388 SkBitmap::kARGB_8888_Config,
389 SkImageDecoder::kDecodePixels_Mode)) {
390 SkDebugf("ERROR: codec failed for <%s>\n",
391 path.c_str());
392 return false;
393 }
394
395 *height = bm.height();
396 *width = bm.width();
397
398 return true;
399}
400
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000401// from gm - thanks to PNG, we need to force all pixels 100% opaque
402static void force_all_opaque(const SkBitmap& bitmap) {
403 SkAutoLockPixels lock(bitmap);
404 for (int y = 0; y < bitmap.height(); y++) {
405 for (int x = 0; x < bitmap.width(); x++) {
406 *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
407 }
408 }
409}
410
411// from gm
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000412static bool write_bitmap(const SkString& path, const SkBitmap* bitmap) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000413 SkBitmap copy;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000414 bitmap->copyTo(&copy, SkBitmap::kARGB_8888_Config);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000415 force_all_opaque(copy);
416 return SkImageEncoder::EncodeFile(path.c_str(), copy,
417 SkImageEncoder::kPNG_Type, 100);
418}
419
420// from gm
421static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
422 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
423 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
424 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
425
426 return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
427}
428
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000429static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1,
430 const int threshold) {
431 int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
432 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
433 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
434 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
435
436 return ((SkAbs32(da) <= threshold) &&
437 (SkAbs32(dr) <= threshold) &&
438 (SkAbs32(dg) <= threshold) &&
439 (SkAbs32(db) <= threshold));
440}
441
442// based on gm
epoger@google.com66008522012-05-16 17:40:57 +0000443// Postcondition: when we exit this method, dr->fResult should have some value
444// other than kUnknown.
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000445static void compute_diff(DiffRecord* dr,
446 DiffMetricProc diffFunction,
447 const int colorThreshold) {
epoger@google.com25d961c2012-02-02 20:50:36 +0000448 SkAutoLockPixels alpDiff(*dr->fDifferenceBitmap);
449 SkAutoLockPixels alpWhite(*dr->fWhiteBitmap);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000450
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000451 const int w = dr->fComparisonBitmap->width();
452 const int h = dr->fComparisonBitmap->height();
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000453 int mismatchedPixels = 0;
454 int totalMismatchR = 0;
455 int totalMismatchG = 0;
456 int totalMismatchB = 0;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000457
458 if (w != dr->fBaseWidth || h != dr->fBaseHeight) {
epoger@google.com292aff62012-05-16 14:57:28 +0000459 dr->fResult = kDifferentSizes;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000460 return;
461 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000462 // Accumulate fractionally different pixels, then divide out
463 // # of pixels at the end.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000464 dr->fWeightedFraction = 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000465 for (int y = 0; y < h; y++) {
466 for (int x = 0; x < w; x++) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000467 SkPMColor c0 = *dr->fBaseBitmap->getAddr32(x, y);
468 SkPMColor c1 = *dr->fComparisonBitmap->getAddr32(x, y);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000469 SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
470 SkPMColor outputDifference = diffFunction(c0, c1);
471 uint32_t thisR = SkGetPackedR32(trueDifference);
472 uint32_t thisG = SkGetPackedG32(trueDifference);
473 uint32_t thisB = SkGetPackedB32(trueDifference);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000474 totalMismatchR += thisR;
475 totalMismatchG += thisG;
476 totalMismatchB += thisB;
477 // In HSV, value is defined as max RGB component.
478 int value = MAX3(thisR, thisG, thisB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000479 dr->fWeightedFraction += ((float) value) / 255;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000480 if (thisR > dr->fMaxMismatchR) {
481 dr->fMaxMismatchR = thisR;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000482 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000483 if (thisG > dr->fMaxMismatchG) {
484 dr->fMaxMismatchG = thisG;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000485 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000486 if (thisB > dr->fMaxMismatchB) {
487 dr->fMaxMismatchB = thisB;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000488 }
489 if (!colors_match_thresholded(c0, c1, colorThreshold)) {
490 mismatchedPixels++;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000491 *dr->fDifferenceBitmap->getAddr32(x, y) = outputDifference;
epoger@google.com25d961c2012-02-02 20:50:36 +0000492 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_WHITE;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000493 } else {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000494 *dr->fDifferenceBitmap->getAddr32(x, y) = 0;
epoger@google.com25d961c2012-02-02 20:50:36 +0000495 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_BLACK;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000496 }
497 }
498 }
epoger@google.com292aff62012-05-16 14:57:28 +0000499 if (0 == mismatchedPixels) {
500 dr->fResult = kEqualPixels;
501 return;
502 }
503 dr->fResult = kDifferentPixels;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000504 int pixelCount = w * h;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000505 dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
506 dr->fWeightedFraction /= pixelCount;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000507 dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
508 dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
509 dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000510}
511
epoger@google.com25d961c2012-02-02 20:50:36 +0000512static SkString filename_to_derived_filename (const SkString& filename,
513 const char *suffix) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000514 SkString diffName (filename);
515 const char* cstring = diffName.c_str();
516 int dotOffset = strrchr(cstring, '.') - cstring;
517 diffName.remove(dotOffset, diffName.size() - dotOffset);
epoger@google.com25d961c2012-02-02 20:50:36 +0000518 diffName.append(suffix);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000519 return diffName;
520}
521
epoger@google.com25d961c2012-02-02 20:50:36 +0000522/// Given a image filename, returns the name of the file containing the
523/// associated difference image.
524static SkString filename_to_diff_filename (const SkString& filename) {
525 return filename_to_derived_filename(filename, "-diff.png");
526}
527
528/// Given a image filename, returns the name of the file containing the
529/// "white" difference image.
530static SkString filename_to_white_filename (const SkString& filename) {
531 return filename_to_derived_filename(filename, "-white.png");
532}
533
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000534static void release_bitmaps(DiffRecord* drp) {
535 delete drp->fBaseBitmap;
536 drp->fBaseBitmap = NULL;
537 delete drp->fComparisonBitmap;
538 drp->fComparisonBitmap = NULL;
539 delete drp->fDifferenceBitmap;
540 drp->fDifferenceBitmap = NULL;
epoger@google.com25d961c2012-02-02 20:50:36 +0000541 delete drp->fWhiteBitmap;
542 drp->fWhiteBitmap = NULL;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000543}
544
tomhudson@google.com7d042802011-07-14 13:15:55 +0000545
epoger@google.coma5f406e2012-05-01 13:26:16 +0000546/// If outputDir.isEmpty(), don't write out diff files.
tomhudson@google.com7d042802011-07-14 13:15:55 +0000547static void create_and_write_diff_image(DiffRecord* drp,
548 DiffMetricProc dmp,
549 const int colorThreshold,
550 const SkString& outputDir,
551 const SkString& filename) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000552 const int w = drp->fBaseWidth;
553 const int h = drp->fBaseHeight;
554 drp->fDifferenceBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
555 drp->fDifferenceBitmap->allocPixels();
epoger@google.com25d961c2012-02-02 20:50:36 +0000556 drp->fWhiteBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
557 drp->fWhiteBitmap->allocPixels();
tomhudson@google.com7d042802011-07-14 13:15:55 +0000558
epoger@google.com66008522012-05-16 17:40:57 +0000559 SkASSERT(kUnknown == drp->fResult);
560 compute_diff(drp, dmp, colorThreshold);
561 SkASSERT(kUnknown != drp->fResult);
562
563 if ((kDifferentPixels == drp->fResult) && !outputDir.isEmpty()) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000564 SkString differencePath (outputDir);
565 differencePath.append(filename_to_diff_filename(filename));
566 write_bitmap(differencePath, drp->fDifferenceBitmap);
567 SkString whitePath (outputDir);
568 whitePath.append(filename_to_white_filename(filename));
569 write_bitmap(whitePath, drp->fWhiteBitmap);
570 }
epoger@google.com66008522012-05-16 17:40:57 +0000571
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000572 release_bitmaps(drp);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000573}
574
epoger@google.coma5f406e2012-05-01 13:26:16 +0000575/// Returns true if string contains any of these substrings.
576static bool string_contains_any_of(const SkString& string,
577 const StringArray& substrings) {
578 for (int i = 0; i < substrings.count(); i++) {
579 if (string.contains(substrings[i]->c_str())) {
580 return true;
581 }
582 }
583 return false;
584}
585
586/// Iterate over dir and get all files that:
587/// - match any of the substrings in matchSubstrings, but...
588/// - DO NOT match any of the substrings in nomatchSubstrings
589/// Returns the list of files in *files.
590static void get_file_list(const SkString& dir,
591 const StringArray& matchSubstrings,
592 const StringArray& nomatchSubstrings,
593 FileArray *files) {
epoger@google.com5fd53852012-03-22 18:20:06 +0000594 SkOSFile::Iter it(dir.c_str());
595 SkString filename;
596 while (it.next(&filename)) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000597 if (string_contains_any_of(filename, matchSubstrings) &&
598 !string_contains_any_of(filename, nomatchSubstrings)) {
599 files->push(new SkString(filename));
epoger@google.com5fd53852012-03-22 18:20:06 +0000600 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000601 }
602}
603
604static void release_file_list(FileArray *files) {
605 files->deleteAll();
606}
607
608/// Comparison routines for qsort, sort by file names.
609static int compare_file_name_metrics(SkString **lhs, SkString **rhs) {
610 return strcmp((*lhs)->c_str(), (*rhs)->c_str());
611}
612
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000613/// Creates difference images, returns the number that have a 0 metric.
epoger@google.coma5f406e2012-05-01 13:26:16 +0000614/// If outputDir.isEmpty(), don't write out diff files.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000615static void create_diff_images (DiffMetricProc dmp,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000616 const int colorThreshold,
617 RecordArray* differences,
618 const SkString& baseDir,
619 const SkString& comparisonDir,
620 const SkString& outputDir,
epoger@google.coma5f406e2012-05-01 13:26:16 +0000621 const StringArray& matchSubstrings,
622 const StringArray& nomatchSubstrings,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000623 DiffSummary* summary) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000624 SkASSERT(!baseDir.isEmpty());
625 SkASSERT(!comparisonDir.isEmpty());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000626
epoger@google.com5fd53852012-03-22 18:20:06 +0000627 FileArray baseFiles;
628 FileArray comparisonFiles;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000629
epoger@google.coma5f406e2012-05-01 13:26:16 +0000630 get_file_list(baseDir, matchSubstrings, nomatchSubstrings, &baseFiles);
631 get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings,
632 &comparisonFiles);
epoger@google.com5fd53852012-03-22 18:20:06 +0000633
epoger@google.coma5f406e2012-05-01 13:26:16 +0000634 if (!baseFiles.isEmpty()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +0000635 qsort(baseFiles.begin(), baseFiles.count(), sizeof(SkString*),
636 SkCastForQSort(compare_file_name_metrics));
epoger@google.coma5f406e2012-05-01 13:26:16 +0000637 }
638 if (!comparisonFiles.isEmpty()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +0000639 qsort(comparisonFiles.begin(), comparisonFiles.count(),
640 sizeof(SkString*), SkCastForQSort(compare_file_name_metrics));
epoger@google.coma5f406e2012-05-01 13:26:16 +0000641 }
epoger@google.com66008522012-05-16 17:40:57 +0000642
epoger@google.com5fd53852012-03-22 18:20:06 +0000643 int i = 0;
644 int j = 0;
645
646 while (i < baseFiles.count() &&
647 j < comparisonFiles.count()) {
648
tomhudson@google.com4e305982011-07-13 17:42:46 +0000649 SkString basePath (baseDir);
epoger@google.com5fd53852012-03-22 18:20:06 +0000650 basePath.append(*baseFiles[i]);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000651 SkString comparisonPath (comparisonDir);
epoger@google.com5fd53852012-03-22 18:20:06 +0000652 comparisonPath.append(*comparisonFiles[j]);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000653
epoger@google.com5fd53852012-03-22 18:20:06 +0000654 DiffRecord *drp = NULL;
655 int v = strcmp(baseFiles[i]->c_str(),
656 comparisonFiles[j]->c_str());
657
658 if (v < 0) {
659 // in baseDir, but not in comparisonDir
epoger@google.com292aff62012-05-16 14:57:28 +0000660 drp = new DiffRecord(*baseFiles[i], basePath, comparisonPath,
661 kComparisonMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000662 ++i;
663 } else if (v > 0) {
664 // in comparisonDir, but not in baseDir
epoger@google.com292aff62012-05-16 14:57:28 +0000665 drp = new DiffRecord(*comparisonFiles[j], basePath, comparisonPath,
666 kBaseMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000667 ++j;
668 } else {
epoger@google.com46256ea2012-05-22 13:45:35 +0000669 // Found the same filename in both baseDir and comparisonDir.
epoger@google.com5fd53852012-03-22 18:20:06 +0000670 drp = new DiffRecord(*baseFiles[i], basePath, comparisonPath);
epoger@google.com46256ea2012-05-22 13:45:35 +0000671 SkASSERT(kUnknown == drp->fResult);
epoger@google.com5fd53852012-03-22 18:20:06 +0000672
epoger@google.com46256ea2012-05-22 13:45:35 +0000673 SkData* baseFileBits;
674 SkData* comparisonFileBits;
675 if (NULL == (baseFileBits = read_file(basePath.c_str()))) {
676 SkDebugf("WARNING: couldn't read base file <%s>\n",
677 basePath.c_str());
678 drp->fResult = kBaseMissing;
679 } else if (NULL == (comparisonFileBits = read_file(
680 comparisonPath.c_str()))) {
681 SkDebugf("WARNING: couldn't read comparison file <%s>\n",
682 comparisonPath.c_str());
683 drp->fResult = kComparisonMissing;
684 } else {
685 if (are_buffers_equal(baseFileBits, comparisonFileBits)) {
686 drp->fResult = kEqualBits;
687 } else if (get_bitmaps(baseFileBits, comparisonFileBits, drp)) {
688 create_and_write_diff_image(drp, dmp, colorThreshold,
689 outputDir, *baseFiles[i]);
690 } else {
691 drp->fResult = kDifferentOther;
692 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000693 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000694 if (baseFileBits) {
695 baseFileBits->unref();
696 }
697 if (comparisonFileBits) {
698 comparisonFileBits->unref();
699 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000700 ++i;
701 ++j;
702 }
epoger@google.com46256ea2012-05-22 13:45:35 +0000703 SkASSERT(kUnknown != drp->fResult);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000704 differences->push(drp);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000705 summary->add(drp);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000706 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000707
708 for (; i < baseFiles.count(); ++i) {
709 // files only in baseDir
710 SkString basePath (baseDir);
711 basePath.append(*baseFiles[i]);
712 SkString comparisonPath;
713 DiffRecord *drp = new DiffRecord(*baseFiles[i], basePath,
epoger@google.com292aff62012-05-16 14:57:28 +0000714 comparisonPath, kComparisonMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000715 differences->push(drp);
716 summary->add(drp);
717 }
718
719 for (; j < comparisonFiles.count(); ++j) {
720 // files only in comparisonDir
721 SkString basePath;
722 SkString comparisonPath(comparisonDir);
723 comparisonPath.append(*comparisonFiles[j]);
724 DiffRecord *drp = new DiffRecord(*comparisonFiles[j], basePath,
epoger@google.com292aff62012-05-16 14:57:28 +0000725 comparisonPath, kBaseMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000726 differences->push(drp);
727 summary->add(drp);
728 }
729
730 release_file_list(&baseFiles);
731 release_file_list(&comparisonFiles);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000732}
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000733
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000734/// Make layout more consistent by scaling image to 240 height, 360 width,
735/// or natural size, whichever is smallest.
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000736static int compute_image_height (int height, int width) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000737 int retval = 240;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000738 if (height < retval) {
739 retval = height;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000740 }
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000741 float scale = (float) retval / height;
742 if (width * scale > 360) {
743 scale = (float) 360 / width;
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000744 retval = static_cast<int>(height * scale);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000745 }
746 return retval;
747}
748
epoger@google.com25d961c2012-02-02 20:50:36 +0000749static void print_table_header (SkFILEWStream* stream,
750 const int matchCount,
751 const int colorThreshold,
752 const RecordArray& differences,
753 const SkString &baseDir,
epoger@google.coma2b793c2012-05-15 14:58:53 +0000754 const SkString &comparisonDir,
755 bool doOutputDate=false) {
epoger@google.com25d961c2012-02-02 20:50:36 +0000756 stream->writeText("<table>\n");
757 stream->writeText("<tr><th>");
epoger@google.coma2b793c2012-05-15 14:58:53 +0000758 if (doOutputDate) {
759 SkTime::DateTime dt;
760 SkTime::GetDateTime(&dt);
761 stream->writeText("SkDiff run at ");
762 stream->writeDecAsText(dt.fHour);
763 stream->writeText(":");
764 if (dt.fMinute < 10) {
765 stream->writeText("0");
766 }
767 stream->writeDecAsText(dt.fMinute);
768 stream->writeText(":");
769 if (dt.fSecond < 10) {
770 stream->writeText("0");
771 }
772 stream->writeDecAsText(dt.fSecond);
773 stream->writeText("<br>");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000774 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000775 stream->writeDecAsText(matchCount);
776 stream->writeText(" of ");
777 stream->writeDecAsText(differences.count());
778 stream->writeText(" images matched ");
779 if (colorThreshold == 0) {
780 stream->writeText("exactly");
781 } else {
782 stream->writeText("within ");
783 stream->writeDecAsText(colorThreshold);
784 stream->writeText(" color units per component");
785 }
786 stream->writeText(".<br>");
epoger@google.com25d961c2012-02-02 20:50:36 +0000787 stream->writeText("</th>\n<th>");
788 stream->writeText("every different pixel shown in white");
789 stream->writeText("</th>\n<th>");
790 stream->writeText("color difference at each pixel");
791 stream->writeText("</th>\n<th>");
792 stream->writeText(baseDir.c_str());
793 stream->writeText("</th>\n<th>");
794 stream->writeText(comparisonDir.c_str());
795 stream->writeText("</th>\n");
796 stream->writeText("</tr>\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000797}
798
799static void print_pixel_count (SkFILEWStream* stream,
800 const DiffRecord& diff) {
801 stream->writeText("<br>(");
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000802 stream->writeDecAsText(static_cast<int>(diff.fFractionDifference *
803 diff.fBaseWidth *
804 diff.fBaseHeight));
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000805 stream->writeText(" pixels)");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000806/*
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000807 stream->writeDecAsText(diff.fWeightedFraction *
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000808 diff.fBaseWidth *
809 diff.fBaseHeight);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000810 stream->writeText(" weighted pixels)");
811*/
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000812}
813
814static void print_label_cell (SkFILEWStream* stream,
815 const DiffRecord& diff) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000816 char metricBuf [20];
817
818 stream->writeText("<td><b>");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000819 stream->writeText(diff.fFilename.c_str());
epoger@google.com46256ea2012-05-22 13:45:35 +0000820 stream->writeText("</b><br>");
epoger@google.com292aff62012-05-16 14:57:28 +0000821 switch (diff.fResult) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000822 case kEqualBits:
823 SkDEBUGFAIL("should not encounter DiffRecord with kEqualBits here");
824 return;
825 case kEqualPixels:
826 SkDEBUGFAIL("should not encounter DiffRecord with kEqualPixels here");
epoger@google.com5fd53852012-03-22 18:20:06 +0000827 return;
epoger@google.com292aff62012-05-16 14:57:28 +0000828 case kDifferentSizes:
epoger@google.com46256ea2012-05-22 13:45:35 +0000829 stream->writeText("Image sizes differ</td>");
830 return;
831 case kDifferentPixels:
832 sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
833 stream->writeText(metricBuf);
834 stream->writeText(" of pixels differ");
835 stream->writeText("\n (");
836 sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
837 stream->writeText(metricBuf);
838 stream->writeText(" weighted)");
839 // Write the actual number of pixels that differ if it's < 1%
840 if (diff.fFractionDifference < 0.01) {
841 print_pixel_count(stream, diff);
842 }
843 stream->writeText("<br>Average color mismatch ");
844 stream->writeDecAsText(static_cast<int>(MAX3(diff.fAverageMismatchR,
845 diff.fAverageMismatchG,
846 diff.fAverageMismatchB)));
847 stream->writeText("<br>Max color mismatch ");
848 stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
849 diff.fMaxMismatchG,
850 diff.fMaxMismatchB));
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000851 stream->writeText("</td>");
epoger@google.com46256ea2012-05-22 13:45:35 +0000852 break;
853 case kDifferentOther:
854 stream->writeText("Files differ; unable to parse one or both files</td>");
855 return;
856 case kBaseMissing:
857 stream->writeText("Missing from baseDir</td>");
858 return;
859 case kComparisonMissing:
860 stream->writeText("Missing from comparisonDir</td>");
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000861 return;
epoger@google.com292aff62012-05-16 14:57:28 +0000862 default:
epoger@google.com46256ea2012-05-22 13:45:35 +0000863 SkDEBUGFAIL("encountered DiffRecord with unknown result type");
864 return;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000865 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000866}
867
868static void print_image_cell (SkFILEWStream* stream,
tomhudson@google.com4e305982011-07-13 17:42:46 +0000869 const SkString& path,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000870 int height) {
871 stream->writeText("<td><a href=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000872 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000873 stream->writeText("\"><img src=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000874 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000875 stream->writeText("\" height=\"");
876 stream->writeDecAsText(height);
877 stream->writeText("px\"></a></td>");
878}
879
epoger@google.com01f78702012-04-12 16:32:04 +0000880static void print_text_cell (SkFILEWStream* stream, const char* text) {
881 stream->writeText("<td align=center>");
882 if (NULL != text) {
883 stream->writeText(text);
884 }
885 stream->writeText("</td>");
886}
887
epoger@google.com5fd53852012-03-22 18:20:06 +0000888static void print_diff_with_missing_file(SkFILEWStream* stream,
889 DiffRecord& diff,
890 const SkString& relativePath) {
891 stream->writeText("<tr>\n");
892 print_label_cell(stream, diff);
893 stream->writeText("<td>N/A</td>");
894 stream->writeText("<td>N/A</td>");
epoger@google.com292aff62012-05-16 14:57:28 +0000895 if (kBaseMissing != diff.fResult) {
epoger@google.com5fd53852012-03-22 18:20:06 +0000896 int h, w;
897 if (!get_bitmap_height_width(diff.fBasePath, &h, &w)) {
898 stream->writeText("<td>N/A</td>");
899 } else {
900 int height = compute_image_height(h, w);
901 if (!diff.fBasePath.startsWith(PATH_DIV_STR)) {
902 diff.fBasePath.prepend(relativePath);
903 }
904 print_image_cell(stream, diff.fBasePath, height);
905 }
906 } else {
907 stream->writeText("<td>N/A</td>");
908 }
epoger@google.com292aff62012-05-16 14:57:28 +0000909 if (kComparisonMissing != diff.fResult) {
epoger@google.com5fd53852012-03-22 18:20:06 +0000910 int h, w;
911 if (!get_bitmap_height_width(diff.fComparisonPath, &h, &w)) {
912 stream->writeText("<td>N/A</td>");
913 } else {
914 int height = compute_image_height(h, w);
915 if (!diff.fComparisonPath.startsWith(PATH_DIV_STR)) {
916 diff.fComparisonPath.prepend(relativePath);
917 }
918 print_image_cell(stream, diff.fComparisonPath, height);
919 }
920 } else {
921 stream->writeText("<td>N/A</td>");
922 }
923 stream->writeText("</tr>\n");
924 stream->flush();
925}
926
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000927static void print_diff_page (const int matchCount,
928 const int colorThreshold,
929 const RecordArray& differences,
930 const SkString& baseDir,
931 const SkString& comparisonDir,
932 const SkString& outputDir) {
933
epoger@google.coma5f406e2012-05-01 13:26:16 +0000934 SkASSERT(!baseDir.isEmpty());
935 SkASSERT(!comparisonDir.isEmpty());
936 SkASSERT(!outputDir.isEmpty());
937
tomhudson@google.com5b325292011-05-24 19:41:13 +0000938 SkString outputPath (outputDir);
939 outputPath.append("index.html");
940 //SkFILEWStream outputStream ("index.html");
941 SkFILEWStream outputStream (outputPath.c_str());
942
tomhudson@google.com4e305982011-07-13 17:42:46 +0000943 // Need to convert paths from relative-to-cwd to relative-to-outputDir
tomhudson@google.com5b325292011-05-24 19:41:13 +0000944 // FIXME this doesn't work if there are '..' inside the outputDir
945 unsigned int ui;
946 SkString relativePath;
947 for (ui = 0; ui < outputDir.size(); ui++) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000948 if (outputDir[ui] == PATH_DIV_CHAR) {
949 relativePath.append(".." PATH_DIV_STR);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000950 }
951 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000952
953 outputStream.writeText("<html>\n<body>\n");
epoger@google.com25d961c2012-02-02 20:50:36 +0000954 print_table_header(&outputStream, matchCount, colorThreshold, differences,
955 baseDir, comparisonDir);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000956 int i;
957 for (i = 0; i < differences.count(); i++) {
958 DiffRecord* diff = differences[i];
epoger@google.com5fd53852012-03-22 18:20:06 +0000959
epoger@google.com292aff62012-05-16 14:57:28 +0000960 switch (diff->fResult) {
epoger@google.com46256ea2012-05-22 13:45:35 +0000961 // Cases in which there is no diff to report.
962 case kEqualBits:
epoger@google.com292aff62012-05-16 14:57:28 +0000963 case kEqualPixels:
964 continue;
epoger@google.com46256ea2012-05-22 13:45:35 +0000965 // Cases in which we want a detailed pixel diff.
966 case kDifferentPixels:
967 break;
968 // Cases in which the files differed, but we can't display the diff.
969 case kDifferentSizes:
970 case kDifferentOther:
epoger@google.com292aff62012-05-16 14:57:28 +0000971 case kBaseMissing:
epoger@google.com292aff62012-05-16 14:57:28 +0000972 case kComparisonMissing:
epoger@google.com5fd53852012-03-22 18:20:06 +0000973 print_diff_with_missing_file(&outputStream, *diff, relativePath);
974 continue;
epoger@google.com292aff62012-05-16 14:57:28 +0000975 default:
epoger@google.com46256ea2012-05-22 13:45:35 +0000976 SkDEBUGFAIL("encountered DiffRecord with unknown result type");
977 continue;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000978 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000979
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000980 if (!diff->fBasePath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000981 diff->fBasePath.prepend(relativePath);
982 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000983 if (!diff->fComparisonPath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000984 diff->fComparisonPath.prepend(relativePath);
985 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000986
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000987 int height = compute_image_height(diff->fBaseHeight, diff->fBaseWidth);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000988 outputStream.writeText("<tr>\n");
989 print_label_cell(&outputStream, *diff);
epoger@google.com46256ea2012-05-22 13:45:35 +0000990 print_image_cell(&outputStream,
991 filename_to_white_filename(diff->fFilename), height);
992 print_image_cell(&outputStream,
993 filename_to_diff_filename(diff->fFilename), height);
epoger@google.com25d961c2012-02-02 20:50:36 +0000994 print_image_cell(&outputStream, diff->fBasePath, height);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000995 print_image_cell(&outputStream, diff->fComparisonPath, height);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000996 outputStream.writeText("</tr>\n");
997 outputStream.flush();
998 }
999 outputStream.writeText("</table>\n");
1000 outputStream.writeText("</body>\n</html>\n");
1001 outputStream.flush();
1002}
1003
1004static void usage (char * argv0) {
1005 SkDebugf("Skia baseline image diff tool\n");
epoger@google.coma5f406e2012-05-01 13:26:16 +00001006 SkDebugf("\n"
1007"Usage: \n"
1008" %s <baseDir> <comparisonDir> [outputDir] \n"
epoger@google.coma611c3e2012-05-18 20:10:06 +00001009, argv0, argv0);
epoger@google.coma5f406e2012-05-01 13:26:16 +00001010 SkDebugf("\n"
1011"Arguments: \n"
1012" --nodiffs: don't write out image diffs or index.html, just generate \n"
1013" report on stdout \n"
1014" --threshold <n>: only report differences > n (per color channel) [default 0]\n"
1015" --match: compare files whose filenames contain this substring; if \n"
1016" unspecified, compare ALL files. \n"
1017" this flag may be repeated to add more matching substrings. \n"
1018" --nomatch: regardless of --match, DO NOT compare files whose filenames \n"
1019" contain this substring. \n"
1020" this flag may be repeated to add more forbidden substrings. \n"
tomhudson@google.com7d042802011-07-14 13:15:55 +00001021" --sortbymismatch: sort by average color channel mismatch\n");
1022 SkDebugf(
epoger@google.coma5f406e2012-05-01 13:26:16 +00001023" --sortbymaxmismatch: sort by worst color channel mismatch;\n"
1024" break ties with -sortbymismatch\n"
1025" [default sort is by fraction of pixels mismatching]\n");
tomhudson@google.com5b325292011-05-24 19:41:13 +00001026 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +00001027" --weighted: sort by # pixels different weighted by color difference\n");
1028 SkDebugf(
epoger@google.coma611c3e2012-05-18 20:10:06 +00001029" baseDir: directory to read baseline images from.\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001030 SkDebugf(
epoger@google.coma5f406e2012-05-01 13:26:16 +00001031" comparisonDir: directory to read comparison images from\n");
1032 SkDebugf(
1033" outputDir: directory to write difference images and index.html to; \n"
epoger@google.coma611c3e2012-05-18 20:10:06 +00001034" defaults to comparisonDir \n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001035}
1036
1037int main (int argc, char ** argv) {
1038 DiffMetricProc diffProc = compute_diff_pmcolor;
reed@google.comc7a67cb2012-05-07 14:52:12 +00001039 int (*sortProc)(const void*, const void*) = SkCastForQSort(compare_diff_metrics);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001040
1041 // Maximum error tolerated in any one color channel in any one pixel before
1042 // a difference is reported.
1043 int colorThreshold = 0;
1044 SkString baseDir;
1045 SkString comparisonDir;
1046 SkString outputDir;
epoger@google.coma5f406e2012-05-01 13:26:16 +00001047 StringArray matchSubstrings;
1048 StringArray nomatchSubstrings;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001049
epoger@google.coma5f406e2012-05-01 13:26:16 +00001050 bool generateDiffs = true;
tomhudson@google.com7d042802011-07-14 13:15:55 +00001051
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001052 RecordArray differences;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001053 DiffSummary summary;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001054
epoger@google.coma5f406e2012-05-01 13:26:16 +00001055 int i;
1056 int numUnflaggedArguments = 0;
1057 for (i = 1; i < argc; i++) {
tomhudson@google.com7d042802011-07-14 13:15:55 +00001058 if (!strcmp(argv[i], "--help")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001059 usage(argv[0]);
1060 return 0;
1061 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001062 if (!strcmp(argv[i], "--nodiffs")) {
1063 generateDiffs = false;
1064 continue;
1065 }
1066 if (!strcmp(argv[i], "--threshold")) {
1067 colorThreshold = atoi(argv[++i]);
1068 continue;
1069 }
1070 if (!strcmp(argv[i], "--match")) {
1071 matchSubstrings.push(new SkString(argv[++i]));
1072 continue;
1073 }
1074 if (!strcmp(argv[i], "--nomatch")) {
1075 nomatchSubstrings.push(new SkString(argv[++i]));
1076 continue;
1077 }
tomhudson@google.com7d042802011-07-14 13:15:55 +00001078 if (!strcmp(argv[i], "--sortbymismatch")) {
reed@google.comc7a67cb2012-05-07 14:52:12 +00001079 sortProc = SkCastForQSort(compare_diff_mean_mismatches);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001080 continue;
1081 }
tomhudson@google.com7d042802011-07-14 13:15:55 +00001082 if (!strcmp(argv[i], "--sortbymaxmismatch")) {
reed@google.comc7a67cb2012-05-07 14:52:12 +00001083 sortProc = SkCastForQSort(compare_diff_max_mismatches);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001084 continue;
1085 }
tomhudson@google.com7d042802011-07-14 13:15:55 +00001086 if (!strcmp(argv[i], "--weighted")) {
reed@google.comc7a67cb2012-05-07 14:52:12 +00001087 sortProc = SkCastForQSort(compare_diff_weighted);
tomhudson@google.com5b325292011-05-24 19:41:13 +00001088 continue;
1089 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001090 if (argv[i][0] != '-') {
epoger@google.coma5f406e2012-05-01 13:26:16 +00001091 switch (numUnflaggedArguments++) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001092 case 0:
1093 baseDir.set(argv[i]);
1094 continue;
1095 case 1:
1096 comparisonDir.set(argv[i]);
1097 continue;
1098 case 2:
1099 outputDir.set(argv[i]);
1100 continue;
1101 default:
epoger@google.coma5f406e2012-05-01 13:26:16 +00001102 SkDebugf("extra unflagged argument <%s>\n", argv[i]);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001103 usage(argv[0]);
1104 return 0;
1105 }
1106 }
1107
1108 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
1109 usage(argv[0]);
1110 return 0;
1111 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001112
epoger@google.coma5f406e2012-05-01 13:26:16 +00001113 if (numUnflaggedArguments == 2) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001114 outputDir = comparisonDir;
epoger@google.coma5f406e2012-05-01 13:26:16 +00001115 } else if (numUnflaggedArguments != 3) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001116 usage(argv[0]);
1117 return 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001118 }
1119
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001120 if (!baseDir.endsWith(PATH_DIV_STR)) {
1121 baseDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001122 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001123 printf("baseDir is [%s]\n", baseDir.c_str());
1124
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001125 if (!comparisonDir.endsWith(PATH_DIV_STR)) {
1126 comparisonDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001127 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001128 printf("comparisonDir is [%s]\n", comparisonDir.c_str());
1129
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001130 if (!outputDir.endsWith(PATH_DIV_STR)) {
1131 outputDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001132 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001133 if (generateDiffs) {
1134 printf("writing diffs to outputDir is [%s]\n", outputDir.c_str());
1135 } else {
1136 printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str());
1137 outputDir.set("");
1138 }
1139
1140 // Default substring matching:
1141 // - No matter what, don't match any PDF files.
1142 // We may want to change this later, but for now this maintains the filter
1143 // that get_file_list() used to always apply.
1144 // - If no matchSubstrings were specified, match ALL strings.
1145 nomatchSubstrings.push(new SkString(".pdf"));
1146 if (matchSubstrings.isEmpty()) {
1147 matchSubstrings.push(new SkString(""));
1148 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001149
epoger@google.coma611c3e2012-05-18 20:10:06 +00001150 create_diff_images(diffProc, colorThreshold, &differences,
1151 baseDir, comparisonDir, outputDir,
1152 matchSubstrings, nomatchSubstrings, &summary);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001153 summary.print();
tomhudson@google.com7d042802011-07-14 13:15:55 +00001154
1155 if (differences.count()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +00001156 qsort(differences.begin(), differences.count(),
1157 sizeof(DiffRecord*), sortProc);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001158 }
epoger@google.com66008522012-05-16 17:40:57 +00001159
epoger@google.coma5f406e2012-05-01 13:26:16 +00001160 if (generateDiffs) {
1161 print_diff_page(summary.fNumMatches, colorThreshold, differences,
1162 baseDir, comparisonDir, outputDir);
1163 }
1164
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001165 for (i = 0; i < differences.count(); i++) {
1166 delete differences[i];
1167 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001168 matchSubstrings.deleteAll();
1169 nomatchSubstrings.deleteAll();
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001170}