blob: 8a2ba2d560d498cc708337ab08ec4520f088a9c7 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2011 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
tomhudson@google.com4b33d282011-04-27 15:39:30 +00008#include "SkColorPriv.h"
9#include "SkImageDecoder.h"
10#include "SkImageEncoder.h"
11#include "SkOSFile.h"
12#include "SkStream.h"
13#include "SkTDArray.h"
14#include "SkTemplates.h"
15#include "SkTime.h"
16#include "SkTSearch.h"
17#include "SkTypes.h"
18
19/**
20 * skdiff
21 *
22 * Given three directory names, expects to find identically-named files in
23 * each of the first two; the first are treated as a set of baseline,
24 * the second a set of variant images, and a diff image is written into the
25 * third directory for each pair.
tomhudson@google.com7d042802011-07-14 13:15:55 +000026 * Creates an index.html in the current third directory to compare each
tomhudson@google.com4b33d282011-04-27 15:39:30 +000027 * pair that does not match exactly.
28 * Does *not* recursively descend directories.
29 */
30
bsalomon@google.com1a315fe2011-09-23 14:56:37 +000031#if SK_BUILD_FOR_WIN32
32 #define PATH_DIV_STR "\\"
33 #define PATH_DIV_CHAR '\\'
34#else
35 #define PATH_DIV_STR "/"
36 #define PATH_DIV_CHAR '/'
37#endif
38
epoger@google.com292aff62012-05-16 14:57:28 +000039// Result of comparison for each pair of files.
40// TODO: we don't actually use all of these yet.
41enum 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.com292aff62012-05-16 14:57:28 +0000162 // Maintain current (and, I think, incorrect) skdiff behavior:
163 // If we were unable to parse either file in the pair as an image,
164 // treat them as matching.
165 // TODO: Remove this logic and change the results for the better.
166 if (kUnknown == drp->fResult) {
167 drp->fResult = kEqualPixels;
168 }
169
170 switch (drp->fResult) {
171 case kEqualPixels:
172 fNumMatches++;
173 break;
174 case kBaseMissing:
epoger@google.com5fd53852012-03-22 18:20:06 +0000175 fBaseMissing.push(new SkString(drp->fFilename));
176 fNumMismatches++;
epoger@google.com292aff62012-05-16 14:57:28 +0000177 break;
178 case kComparisonMissing:
epoger@google.com5fd53852012-03-22 18:20:06 +0000179 fComparisonMissing.push(new SkString(drp->fFilename));
180 fNumMismatches++;
epoger@google.com292aff62012-05-16 14:57:28 +0000181 break;
182 default:
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000183 fNumMismatches++;
184 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
185 fMaxMismatchPercent = drp->fFractionDifference * 100;
186 }
tomhudson@google.com88a0e052011-06-09 18:54:01 +0000187 uint32_t value = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
188 drp->fMaxMismatchB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000189 if (value > fMaxMismatchV) {
190 fMaxMismatchV = value;
191 }
epoger@google.com292aff62012-05-16 14:57:28 +0000192 break;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000193 }
194 }
195};
196
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000197typedef SkTDArray<DiffRecord*> RecordArray;
198
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000199/// Comparison routine for qsort; sorts by fFractionDifference
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000200/// from largest to smallest.
201static int compare_diff_metrics (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000202 if ((*lhs)->fFractionDifference < (*rhs)->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000203 return 1;
204 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000205 if ((*rhs)->fFractionDifference < (*lhs)->fFractionDifference) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000206 return -1;
207 }
208 return 0;
209}
210
211static int compare_diff_weighted (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000212 if ((*lhs)->fWeightedFraction < (*rhs)->fWeightedFraction) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000213 return 1;
214 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000215 if ((*lhs)->fWeightedFraction > (*rhs)->fWeightedFraction) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000216 return -1;
217 }
218 return 0;
219}
220
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000221/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
222/// from largest to smallest.
223static int compare_diff_mean_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
224 float leftValue = MAX3((*lhs)->fAverageMismatchR,
225 (*lhs)->fAverageMismatchG,
226 (*lhs)->fAverageMismatchB);
227 float rightValue = MAX3((*rhs)->fAverageMismatchR,
228 (*rhs)->fAverageMismatchG,
229 (*rhs)->fAverageMismatchB);
230 if (leftValue < rightValue) {
231 return 1;
232 }
233 if (rightValue < leftValue) {
234 return -1;
235 }
236 return 0;
237}
238
239/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB})
240/// from largest to smallest.
241static int compare_diff_max_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000242 uint32_t leftValue = MAX3((*lhs)->fMaxMismatchR,
243 (*lhs)->fMaxMismatchG,
244 (*lhs)->fMaxMismatchB);
245 uint32_t rightValue = MAX3((*rhs)->fMaxMismatchR,
246 (*rhs)->fMaxMismatchG,
247 (*rhs)->fMaxMismatchB);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000248 if (leftValue < rightValue) {
249 return 1;
250 }
251 if (rightValue < leftValue) {
252 return -1;
253 }
254 return compare_diff_mean_mismatches(lhs, rhs);
255}
256
257
258
259/// Parameterized routine to compute the color of a pixel in a difference image.
260typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
261
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000262static void expand_and_copy (int width, int height, SkBitmap** dest) {
263 SkBitmap* temp = new SkBitmap ();
264 temp->reset();
265 temp->setConfig((*dest)->config(), width, height);
266 temp->allocPixels();
267 (*dest)->copyPixelsTo(temp->getPixels(), temp->getSize(),
268 temp->rowBytes());
269 *dest = temp;
270}
271
tomhudson@google.com4e305982011-07-13 17:42:46 +0000272static bool get_bitmaps (DiffRecord* diffRecord) {
273 SkFILEStream compareStream(diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000274 if (!compareStream.isValid()) {
275 SkDebugf("WARNING: couldn't open comparison file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000276 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000277 return false;
278 }
279
tomhudson@google.com4e305982011-07-13 17:42:46 +0000280 SkFILEStream baseStream(diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000281 if (!baseStream.isValid()) {
282 SkDebugf("ERROR: couldn't open base file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000283 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000284 return false;
285 }
286
287 SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
288 if (NULL == codec) {
289 SkDebugf("ERROR: no codec found for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000290 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000291 return false;
292 }
293
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000294 // In debug, the DLL will automatically be unloaded when this is deleted,
295 // but that shouldn't be a problem in release mode.
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000296 SkAutoTDelete<SkImageDecoder> ad(codec);
297
298 baseStream.rewind();
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000299 if (!codec->decode(&baseStream, diffRecord->fBaseBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000300 SkBitmap::kARGB_8888_Config,
301 SkImageDecoder::kDecodePixels_Mode)) {
302 SkDebugf("ERROR: codec failed for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000303 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000304 return false;
305 }
306
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000307 diffRecord->fBaseWidth = diffRecord->fBaseBitmap->width();
308 diffRecord->fBaseHeight = diffRecord->fBaseBitmap->height();
309
310 if (!codec->decode(&compareStream, diffRecord->fComparisonBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000311 SkBitmap::kARGB_8888_Config,
312 SkImageDecoder::kDecodePixels_Mode)) {
313 SkDebugf("ERROR: codec failed for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000314 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000315 return false;
316 }
317
318 return true;
319}
320
epoger@google.com5fd53852012-03-22 18:20:06 +0000321static bool get_bitmap_height_width(const SkString& path,
322 int *height, int *width) {
323 SkFILEStream stream(path.c_str());
324 if (!stream.isValid()) {
325 SkDebugf("ERROR: couldn't open file <%s>\n",
326 path.c_str());
327 return false;
328 }
329
330 SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
331 if (NULL == codec) {
332 SkDebugf("ERROR: no codec found for <%s>\n",
333 path.c_str());
334 return false;
335 }
336
337 SkAutoTDelete<SkImageDecoder> ad(codec);
338 SkBitmap bm;
339
340 stream.rewind();
341 if (!codec->decode(&stream, &bm,
342 SkBitmap::kARGB_8888_Config,
343 SkImageDecoder::kDecodePixels_Mode)) {
344 SkDebugf("ERROR: codec failed for <%s>\n",
345 path.c_str());
346 return false;
347 }
348
349 *height = bm.height();
350 *width = bm.width();
351
352 return true;
353}
354
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000355// from gm - thanks to PNG, we need to force all pixels 100% opaque
356static void force_all_opaque(const SkBitmap& bitmap) {
357 SkAutoLockPixels lock(bitmap);
358 for (int y = 0; y < bitmap.height(); y++) {
359 for (int x = 0; x < bitmap.width(); x++) {
360 *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
361 }
362 }
363}
364
365// from gm
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000366static bool write_bitmap(const SkString& path, const SkBitmap* bitmap) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000367 SkBitmap copy;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000368 bitmap->copyTo(&copy, SkBitmap::kARGB_8888_Config);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000369 force_all_opaque(copy);
370 return SkImageEncoder::EncodeFile(path.c_str(), copy,
371 SkImageEncoder::kPNG_Type, 100);
372}
373
374// from gm
375static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
376 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
377 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
378 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
379
380 return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
381}
382
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000383static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1,
384 const int threshold) {
385 int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
386 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
387 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
388 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
389
390 return ((SkAbs32(da) <= threshold) &&
391 (SkAbs32(dr) <= threshold) &&
392 (SkAbs32(dg) <= threshold) &&
393 (SkAbs32(db) <= threshold));
394}
395
396// based on gm
epoger@google.com66008522012-05-16 17:40:57 +0000397// Postcondition: when we exit this method, dr->fResult should have some value
398// other than kUnknown.
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000399static void compute_diff(DiffRecord* dr,
400 DiffMetricProc diffFunction,
401 const int colorThreshold) {
epoger@google.com25d961c2012-02-02 20:50:36 +0000402 SkAutoLockPixels alpDiff(*dr->fDifferenceBitmap);
403 SkAutoLockPixels alpWhite(*dr->fWhiteBitmap);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000404
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000405 const int w = dr->fComparisonBitmap->width();
406 const int h = dr->fComparisonBitmap->height();
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000407 int mismatchedPixels = 0;
408 int totalMismatchR = 0;
409 int totalMismatchG = 0;
410 int totalMismatchB = 0;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000411
412 if (w != dr->fBaseWidth || h != dr->fBaseHeight) {
epoger@google.com292aff62012-05-16 14:57:28 +0000413 dr->fResult = kDifferentSizes;
414 dr->fFractionDifference = 1; // for sorting the diffs later
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000415 return;
416 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000417 // Accumulate fractionally different pixels, then divide out
418 // # of pixels at the end.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000419 dr->fWeightedFraction = 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000420 for (int y = 0; y < h; y++) {
421 for (int x = 0; x < w; x++) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000422 SkPMColor c0 = *dr->fBaseBitmap->getAddr32(x, y);
423 SkPMColor c1 = *dr->fComparisonBitmap->getAddr32(x, y);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000424 SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
425 SkPMColor outputDifference = diffFunction(c0, c1);
426 uint32_t thisR = SkGetPackedR32(trueDifference);
427 uint32_t thisG = SkGetPackedG32(trueDifference);
428 uint32_t thisB = SkGetPackedB32(trueDifference);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000429 totalMismatchR += thisR;
430 totalMismatchG += thisG;
431 totalMismatchB += thisB;
432 // In HSV, value is defined as max RGB component.
433 int value = MAX3(thisR, thisG, thisB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000434 dr->fWeightedFraction += ((float) value) / 255;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000435 if (thisR > dr->fMaxMismatchR) {
436 dr->fMaxMismatchR = thisR;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000437 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000438 if (thisG > dr->fMaxMismatchG) {
439 dr->fMaxMismatchG = thisG;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000440 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000441 if (thisB > dr->fMaxMismatchB) {
442 dr->fMaxMismatchB = thisB;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000443 }
444 if (!colors_match_thresholded(c0, c1, colorThreshold)) {
445 mismatchedPixels++;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000446 *dr->fDifferenceBitmap->getAddr32(x, y) = outputDifference;
epoger@google.com25d961c2012-02-02 20:50:36 +0000447 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_WHITE;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000448 } else {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000449 *dr->fDifferenceBitmap->getAddr32(x, y) = 0;
epoger@google.com25d961c2012-02-02 20:50:36 +0000450 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_BLACK;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000451 }
452 }
453 }
epoger@google.com292aff62012-05-16 14:57:28 +0000454 if (0 == mismatchedPixels) {
455 dr->fResult = kEqualPixels;
456 return;
457 }
458 dr->fResult = kDifferentPixels;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000459 int pixelCount = w * h;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000460 dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
461 dr->fWeightedFraction /= pixelCount;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000462 dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
463 dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
464 dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000465}
466
epoger@google.com25d961c2012-02-02 20:50:36 +0000467static SkString filename_to_derived_filename (const SkString& filename,
468 const char *suffix) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000469 SkString diffName (filename);
470 const char* cstring = diffName.c_str();
471 int dotOffset = strrchr(cstring, '.') - cstring;
472 diffName.remove(dotOffset, diffName.size() - dotOffset);
epoger@google.com25d961c2012-02-02 20:50:36 +0000473 diffName.append(suffix);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000474 return diffName;
475}
476
epoger@google.com25d961c2012-02-02 20:50:36 +0000477/// Given a image filename, returns the name of the file containing the
478/// associated difference image.
479static SkString filename_to_diff_filename (const SkString& filename) {
480 return filename_to_derived_filename(filename, "-diff.png");
481}
482
483/// Given a image filename, returns the name of the file containing the
484/// "white" difference image.
485static SkString filename_to_white_filename (const SkString& filename) {
486 return filename_to_derived_filename(filename, "-white.png");
487}
488
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000489static void release_bitmaps(DiffRecord* drp) {
490 delete drp->fBaseBitmap;
491 drp->fBaseBitmap = NULL;
492 delete drp->fComparisonBitmap;
493 drp->fComparisonBitmap = NULL;
494 delete drp->fDifferenceBitmap;
495 drp->fDifferenceBitmap = NULL;
epoger@google.com25d961c2012-02-02 20:50:36 +0000496 delete drp->fWhiteBitmap;
497 drp->fWhiteBitmap = NULL;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000498}
499
tomhudson@google.com7d042802011-07-14 13:15:55 +0000500
epoger@google.coma5f406e2012-05-01 13:26:16 +0000501/// If outputDir.isEmpty(), don't write out diff files.
tomhudson@google.com7d042802011-07-14 13:15:55 +0000502static void create_and_write_diff_image(DiffRecord* drp,
503 DiffMetricProc dmp,
504 const int colorThreshold,
505 const SkString& outputDir,
506 const SkString& filename) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000507 const int w = drp->fBaseWidth;
508 const int h = drp->fBaseHeight;
509 drp->fDifferenceBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
510 drp->fDifferenceBitmap->allocPixels();
epoger@google.com25d961c2012-02-02 20:50:36 +0000511 drp->fWhiteBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
512 drp->fWhiteBitmap->allocPixels();
tomhudson@google.com7d042802011-07-14 13:15:55 +0000513
epoger@google.com66008522012-05-16 17:40:57 +0000514 SkASSERT(kUnknown == drp->fResult);
515 compute_diff(drp, dmp, colorThreshold);
516 SkASSERT(kUnknown != drp->fResult);
517
518 if ((kDifferentPixels == drp->fResult) && !outputDir.isEmpty()) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000519 SkString differencePath (outputDir);
520 differencePath.append(filename_to_diff_filename(filename));
521 write_bitmap(differencePath, drp->fDifferenceBitmap);
522 SkString whitePath (outputDir);
523 whitePath.append(filename_to_white_filename(filename));
524 write_bitmap(whitePath, drp->fWhiteBitmap);
525 }
epoger@google.com66008522012-05-16 17:40:57 +0000526
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000527 release_bitmaps(drp);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000528}
529
epoger@google.coma5f406e2012-05-01 13:26:16 +0000530/// Returns true if string contains any of these substrings.
531static bool string_contains_any_of(const SkString& string,
532 const StringArray& substrings) {
533 for (int i = 0; i < substrings.count(); i++) {
534 if (string.contains(substrings[i]->c_str())) {
535 return true;
536 }
537 }
538 return false;
539}
540
541/// Iterate over dir and get all files that:
542/// - match any of the substrings in matchSubstrings, but...
543/// - DO NOT match any of the substrings in nomatchSubstrings
544/// Returns the list of files in *files.
545static void get_file_list(const SkString& dir,
546 const StringArray& matchSubstrings,
547 const StringArray& nomatchSubstrings,
548 FileArray *files) {
epoger@google.com5fd53852012-03-22 18:20:06 +0000549 SkOSFile::Iter it(dir.c_str());
550 SkString filename;
551 while (it.next(&filename)) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000552 if (string_contains_any_of(filename, matchSubstrings) &&
553 !string_contains_any_of(filename, nomatchSubstrings)) {
554 files->push(new SkString(filename));
epoger@google.com5fd53852012-03-22 18:20:06 +0000555 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000556 }
557}
558
559static void release_file_list(FileArray *files) {
560 files->deleteAll();
561}
562
563/// Comparison routines for qsort, sort by file names.
564static int compare_file_name_metrics(SkString **lhs, SkString **rhs) {
565 return strcmp((*lhs)->c_str(), (*rhs)->c_str());
566}
567
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000568/// Creates difference images, returns the number that have a 0 metric.
epoger@google.coma5f406e2012-05-01 13:26:16 +0000569/// If outputDir.isEmpty(), don't write out diff files.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000570static void create_diff_images (DiffMetricProc dmp,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000571 const int colorThreshold,
572 RecordArray* differences,
573 const SkString& baseDir,
574 const SkString& comparisonDir,
575 const SkString& outputDir,
epoger@google.coma5f406e2012-05-01 13:26:16 +0000576 const StringArray& matchSubstrings,
577 const StringArray& nomatchSubstrings,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000578 DiffSummary* summary) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000579 SkASSERT(!baseDir.isEmpty());
580 SkASSERT(!comparisonDir.isEmpty());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000581
epoger@google.com5fd53852012-03-22 18:20:06 +0000582 FileArray baseFiles;
583 FileArray comparisonFiles;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000584
epoger@google.coma5f406e2012-05-01 13:26:16 +0000585 get_file_list(baseDir, matchSubstrings, nomatchSubstrings, &baseFiles);
586 get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings,
587 &comparisonFiles);
epoger@google.com5fd53852012-03-22 18:20:06 +0000588
epoger@google.coma5f406e2012-05-01 13:26:16 +0000589 if (!baseFiles.isEmpty()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +0000590 qsort(baseFiles.begin(), baseFiles.count(), sizeof(SkString*),
591 SkCastForQSort(compare_file_name_metrics));
epoger@google.coma5f406e2012-05-01 13:26:16 +0000592 }
593 if (!comparisonFiles.isEmpty()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +0000594 qsort(comparisonFiles.begin(), comparisonFiles.count(),
595 sizeof(SkString*), SkCastForQSort(compare_file_name_metrics));
epoger@google.coma5f406e2012-05-01 13:26:16 +0000596 }
epoger@google.com66008522012-05-16 17:40:57 +0000597
epoger@google.com5fd53852012-03-22 18:20:06 +0000598 int i = 0;
599 int j = 0;
600
601 while (i < baseFiles.count() &&
602 j < comparisonFiles.count()) {
603
tomhudson@google.com4e305982011-07-13 17:42:46 +0000604 SkString basePath (baseDir);
epoger@google.com5fd53852012-03-22 18:20:06 +0000605 basePath.append(*baseFiles[i]);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000606 SkString comparisonPath (comparisonDir);
epoger@google.com5fd53852012-03-22 18:20:06 +0000607 comparisonPath.append(*comparisonFiles[j]);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000608
epoger@google.com5fd53852012-03-22 18:20:06 +0000609 DiffRecord *drp = NULL;
610 int v = strcmp(baseFiles[i]->c_str(),
611 comparisonFiles[j]->c_str());
612
613 if (v < 0) {
614 // in baseDir, but not in comparisonDir
epoger@google.com292aff62012-05-16 14:57:28 +0000615 drp = new DiffRecord(*baseFiles[i], basePath, comparisonPath,
616 kComparisonMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000617 ++i;
618 } else if (v > 0) {
619 // in comparisonDir, but not in baseDir
epoger@google.com292aff62012-05-16 14:57:28 +0000620 drp = new DiffRecord(*comparisonFiles[j], basePath, comparisonPath,
621 kBaseMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000622 ++j;
623 } else {
624 // let's diff!
625 drp = new DiffRecord(*baseFiles[i], basePath, comparisonPath);
626
627 if (get_bitmaps(drp)) {
628 create_and_write_diff_image(drp, dmp, colorThreshold,
629 outputDir, *baseFiles[i]);
630 }
631
632 ++i;
633 ++j;
634 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000635
636 differences->push(drp);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000637 summary->add(drp);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000638 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000639
640 for (; i < baseFiles.count(); ++i) {
641 // files only in baseDir
642 SkString basePath (baseDir);
643 basePath.append(*baseFiles[i]);
644 SkString comparisonPath;
645 DiffRecord *drp = new DiffRecord(*baseFiles[i], basePath,
epoger@google.com292aff62012-05-16 14:57:28 +0000646 comparisonPath, kComparisonMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000647 differences->push(drp);
648 summary->add(drp);
649 }
650
651 for (; j < comparisonFiles.count(); ++j) {
652 // files only in comparisonDir
653 SkString basePath;
654 SkString comparisonPath(comparisonDir);
655 comparisonPath.append(*comparisonFiles[j]);
656 DiffRecord *drp = new DiffRecord(*comparisonFiles[j], basePath,
epoger@google.com292aff62012-05-16 14:57:28 +0000657 comparisonPath, kBaseMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000658 differences->push(drp);
659 summary->add(drp);
660 }
661
662 release_file_list(&baseFiles);
663 release_file_list(&comparisonFiles);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000664}
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000665
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000666/// Make layout more consistent by scaling image to 240 height, 360 width,
667/// or natural size, whichever is smallest.
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000668static int compute_image_height (int height, int width) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000669 int retval = 240;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000670 if (height < retval) {
671 retval = height;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000672 }
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000673 float scale = (float) retval / height;
674 if (width * scale > 360) {
675 scale = (float) 360 / width;
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000676 retval = static_cast<int>(height * scale);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000677 }
678 return retval;
679}
680
epoger@google.com25d961c2012-02-02 20:50:36 +0000681static void print_table_header (SkFILEWStream* stream,
682 const int matchCount,
683 const int colorThreshold,
684 const RecordArray& differences,
685 const SkString &baseDir,
epoger@google.coma2b793c2012-05-15 14:58:53 +0000686 const SkString &comparisonDir,
687 bool doOutputDate=false) {
epoger@google.com25d961c2012-02-02 20:50:36 +0000688 stream->writeText("<table>\n");
689 stream->writeText("<tr><th>");
epoger@google.coma2b793c2012-05-15 14:58:53 +0000690 if (doOutputDate) {
691 SkTime::DateTime dt;
692 SkTime::GetDateTime(&dt);
693 stream->writeText("SkDiff run at ");
694 stream->writeDecAsText(dt.fHour);
695 stream->writeText(":");
696 if (dt.fMinute < 10) {
697 stream->writeText("0");
698 }
699 stream->writeDecAsText(dt.fMinute);
700 stream->writeText(":");
701 if (dt.fSecond < 10) {
702 stream->writeText("0");
703 }
704 stream->writeDecAsText(dt.fSecond);
705 stream->writeText("<br>");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000706 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000707 stream->writeDecAsText(matchCount);
708 stream->writeText(" of ");
709 stream->writeDecAsText(differences.count());
710 stream->writeText(" images matched ");
711 if (colorThreshold == 0) {
712 stream->writeText("exactly");
713 } else {
714 stream->writeText("within ");
715 stream->writeDecAsText(colorThreshold);
716 stream->writeText(" color units per component");
717 }
718 stream->writeText(".<br>");
epoger@google.com25d961c2012-02-02 20:50:36 +0000719 stream->writeText("</th>\n<th>");
720 stream->writeText("every different pixel shown in white");
721 stream->writeText("</th>\n<th>");
722 stream->writeText("color difference at each pixel");
723 stream->writeText("</th>\n<th>");
724 stream->writeText(baseDir.c_str());
725 stream->writeText("</th>\n<th>");
726 stream->writeText(comparisonDir.c_str());
727 stream->writeText("</th>\n");
728 stream->writeText("</tr>\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000729}
730
731static void print_pixel_count (SkFILEWStream* stream,
732 const DiffRecord& diff) {
733 stream->writeText("<br>(");
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000734 stream->writeDecAsText(static_cast<int>(diff.fFractionDifference *
735 diff.fBaseWidth *
736 diff.fBaseHeight));
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000737 stream->writeText(" pixels)");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000738/*
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000739 stream->writeDecAsText(diff.fWeightedFraction *
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000740 diff.fBaseWidth *
741 diff.fBaseHeight);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000742 stream->writeText(" weighted pixels)");
743*/
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000744}
745
746static void print_label_cell (SkFILEWStream* stream,
747 const DiffRecord& diff) {
748 stream->writeText("<td>");
749 stream->writeText(diff.fFilename.c_str());
750 stream->writeText("<br>");
epoger@google.com292aff62012-05-16 14:57:28 +0000751 switch (diff.fResult) {
752 case kBaseMissing:
753 // fall through
754 case kComparisonMissing:
epoger@google.com5fd53852012-03-22 18:20:06 +0000755 stream->writeText("</td>");
756 return;
epoger@google.com292aff62012-05-16 14:57:28 +0000757 case kDifferentSizes:
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000758 stream->writeText("Image sizes differ");
759 stream->writeText("</td>");
760 return;
epoger@google.com292aff62012-05-16 14:57:28 +0000761 default:
762 break; // continue in this function
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000763 }
epoger@google.com292aff62012-05-16 14:57:28 +0000764
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000765 char metricBuf [20];
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000766 sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000767 stream->writeText(metricBuf);
768 stream->writeText(" of pixels differ");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000769 stream->writeText("\n (");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000770 sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000771 stream->writeText(metricBuf);
772 stream->writeText(" weighted)");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000773 // Write the actual number of pixels that differ if it's < 1%
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000774 if (diff.fFractionDifference < 0.01) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000775 print_pixel_count(stream, diff);
776 }
777 stream->writeText("<br>Average color mismatch ");
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000778 stream->writeDecAsText(static_cast<int>(MAX3(diff.fAverageMismatchR,
779 diff.fAverageMismatchG,
780 diff.fAverageMismatchB)));
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000781 stream->writeText("<br>Max color mismatch ");
782 stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
783 diff.fMaxMismatchG,
784 diff.fMaxMismatchB));
785 stream->writeText("</td>");
786}
787
788static void print_image_cell (SkFILEWStream* stream,
tomhudson@google.com4e305982011-07-13 17:42:46 +0000789 const SkString& path,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000790 int height) {
791 stream->writeText("<td><a href=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000792 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000793 stream->writeText("\"><img src=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000794 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000795 stream->writeText("\" height=\"");
796 stream->writeDecAsText(height);
797 stream->writeText("px\"></a></td>");
798}
799
epoger@google.com01f78702012-04-12 16:32:04 +0000800static void print_text_cell (SkFILEWStream* stream, const char* text) {
801 stream->writeText("<td align=center>");
802 if (NULL != text) {
803 stream->writeText(text);
804 }
805 stream->writeText("</td>");
806}
807
epoger@google.com5fd53852012-03-22 18:20:06 +0000808static void print_diff_with_missing_file(SkFILEWStream* stream,
809 DiffRecord& diff,
810 const SkString& relativePath) {
811 stream->writeText("<tr>\n");
812 print_label_cell(stream, diff);
813 stream->writeText("<td>N/A</td>");
814 stream->writeText("<td>N/A</td>");
epoger@google.com292aff62012-05-16 14:57:28 +0000815 if (kBaseMissing != diff.fResult) {
epoger@google.com5fd53852012-03-22 18:20:06 +0000816 int h, w;
817 if (!get_bitmap_height_width(diff.fBasePath, &h, &w)) {
818 stream->writeText("<td>N/A</td>");
819 } else {
820 int height = compute_image_height(h, w);
821 if (!diff.fBasePath.startsWith(PATH_DIV_STR)) {
822 diff.fBasePath.prepend(relativePath);
823 }
824 print_image_cell(stream, diff.fBasePath, height);
825 }
826 } else {
827 stream->writeText("<td>N/A</td>");
828 }
epoger@google.com292aff62012-05-16 14:57:28 +0000829 if (kComparisonMissing != diff.fResult) {
epoger@google.com5fd53852012-03-22 18:20:06 +0000830 int h, w;
831 if (!get_bitmap_height_width(diff.fComparisonPath, &h, &w)) {
832 stream->writeText("<td>N/A</td>");
833 } else {
834 int height = compute_image_height(h, w);
835 if (!diff.fComparisonPath.startsWith(PATH_DIV_STR)) {
836 diff.fComparisonPath.prepend(relativePath);
837 }
838 print_image_cell(stream, diff.fComparisonPath, height);
839 }
840 } else {
841 stream->writeText("<td>N/A</td>");
842 }
843 stream->writeText("</tr>\n");
844 stream->flush();
845}
846
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000847static void print_diff_page (const int matchCount,
848 const int colorThreshold,
849 const RecordArray& differences,
850 const SkString& baseDir,
851 const SkString& comparisonDir,
852 const SkString& outputDir) {
853
epoger@google.coma5f406e2012-05-01 13:26:16 +0000854 SkASSERT(!baseDir.isEmpty());
855 SkASSERT(!comparisonDir.isEmpty());
856 SkASSERT(!outputDir.isEmpty());
857
tomhudson@google.com5b325292011-05-24 19:41:13 +0000858 SkString outputPath (outputDir);
859 outputPath.append("index.html");
860 //SkFILEWStream outputStream ("index.html");
861 SkFILEWStream outputStream (outputPath.c_str());
862
tomhudson@google.com4e305982011-07-13 17:42:46 +0000863 // Need to convert paths from relative-to-cwd to relative-to-outputDir
tomhudson@google.com5b325292011-05-24 19:41:13 +0000864 // FIXME this doesn't work if there are '..' inside the outputDir
865 unsigned int ui;
866 SkString relativePath;
867 for (ui = 0; ui < outputDir.size(); ui++) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000868 if (outputDir[ui] == PATH_DIV_CHAR) {
869 relativePath.append(".." PATH_DIV_STR);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000870 }
871 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000872
873 outputStream.writeText("<html>\n<body>\n");
epoger@google.com25d961c2012-02-02 20:50:36 +0000874 print_table_header(&outputStream, matchCount, colorThreshold, differences,
875 baseDir, comparisonDir);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000876 int i;
877 for (i = 0; i < differences.count(); i++) {
878 DiffRecord* diff = differences[i];
epoger@google.com5fd53852012-03-22 18:20:06 +0000879
epoger@google.com292aff62012-05-16 14:57:28 +0000880 switch (diff->fResult) {
881 case kEqualPixels:
882 continue;
883 case kBaseMissing:
884 // fall through
885 case kComparisonMissing:
epoger@google.com5fd53852012-03-22 18:20:06 +0000886 print_diff_with_missing_file(&outputStream, *diff, relativePath);
887 continue;
epoger@google.com292aff62012-05-16 14:57:28 +0000888 default:
889 break;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000890 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000891
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000892 if (!diff->fBasePath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000893 diff->fBasePath.prepend(relativePath);
894 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000895 if (!diff->fComparisonPath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000896 diff->fComparisonPath.prepend(relativePath);
897 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000898
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000899 int height = compute_image_height(diff->fBaseHeight, diff->fBaseWidth);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000900 outputStream.writeText("<tr>\n");
901 print_label_cell(&outputStream, *diff);
epoger@google.com292aff62012-05-16 14:57:28 +0000902 switch (diff->fResult) {
903 case kDifferentSizes:
epoger@google.com01f78702012-04-12 16:32:04 +0000904 print_text_cell(&outputStream,
905 "[image size mismatch, so no diff to display]");
906 print_text_cell(&outputStream,
907 "[image size mismatch, so no diff to display]");
epoger@google.com292aff62012-05-16 14:57:28 +0000908 break;
909 default:
epoger@google.com01f78702012-04-12 16:32:04 +0000910 print_image_cell(&outputStream,
911 filename_to_white_filename(diff->fFilename), height);
912 print_image_cell(&outputStream,
913 filename_to_diff_filename(diff->fFilename), height);
914 }
epoger@google.com25d961c2012-02-02 20:50:36 +0000915 print_image_cell(&outputStream, diff->fBasePath, height);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000916 print_image_cell(&outputStream, diff->fComparisonPath, height);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000917 outputStream.writeText("</tr>\n");
918 outputStream.flush();
919 }
920 outputStream.writeText("</table>\n");
921 outputStream.writeText("</body>\n</html>\n");
922 outputStream.flush();
923}
924
925static void usage (char * argv0) {
926 SkDebugf("Skia baseline image diff tool\n");
epoger@google.coma5f406e2012-05-01 13:26:16 +0000927 SkDebugf("\n"
928"Usage: \n"
929" %s <baseDir> <comparisonDir> [outputDir] \n"
epoger@google.coma611c3e2012-05-18 20:10:06 +0000930, argv0, argv0);
epoger@google.coma5f406e2012-05-01 13:26:16 +0000931 SkDebugf("\n"
932"Arguments: \n"
933" --nodiffs: don't write out image diffs or index.html, just generate \n"
934" report on stdout \n"
935" --threshold <n>: only report differences > n (per color channel) [default 0]\n"
936" --match: compare files whose filenames contain this substring; if \n"
937" unspecified, compare ALL files. \n"
938" this flag may be repeated to add more matching substrings. \n"
939" --nomatch: regardless of --match, DO NOT compare files whose filenames \n"
940" contain this substring. \n"
941" this flag may be repeated to add more forbidden substrings. \n"
tomhudson@google.com7d042802011-07-14 13:15:55 +0000942" --sortbymismatch: sort by average color channel mismatch\n");
943 SkDebugf(
epoger@google.coma5f406e2012-05-01 13:26:16 +0000944" --sortbymaxmismatch: sort by worst color channel mismatch;\n"
945" break ties with -sortbymismatch\n"
946" [default sort is by fraction of pixels mismatching]\n");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000947 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000948" --weighted: sort by # pixels different weighted by color difference\n");
949 SkDebugf(
epoger@google.coma611c3e2012-05-18 20:10:06 +0000950" baseDir: directory to read baseline images from.\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000951 SkDebugf(
epoger@google.coma5f406e2012-05-01 13:26:16 +0000952" comparisonDir: directory to read comparison images from\n");
953 SkDebugf(
954" outputDir: directory to write difference images and index.html to; \n"
epoger@google.coma611c3e2012-05-18 20:10:06 +0000955" defaults to comparisonDir \n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000956}
957
958int main (int argc, char ** argv) {
959 DiffMetricProc diffProc = compute_diff_pmcolor;
reed@google.comc7a67cb2012-05-07 14:52:12 +0000960 int (*sortProc)(const void*, const void*) = SkCastForQSort(compare_diff_metrics);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000961
962 // Maximum error tolerated in any one color channel in any one pixel before
963 // a difference is reported.
964 int colorThreshold = 0;
965 SkString baseDir;
966 SkString comparisonDir;
967 SkString outputDir;
epoger@google.coma5f406e2012-05-01 13:26:16 +0000968 StringArray matchSubstrings;
969 StringArray nomatchSubstrings;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000970
epoger@google.coma5f406e2012-05-01 13:26:16 +0000971 bool generateDiffs = true;
tomhudson@google.com7d042802011-07-14 13:15:55 +0000972
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000973 RecordArray differences;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000974 DiffSummary summary;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000975
epoger@google.coma5f406e2012-05-01 13:26:16 +0000976 int i;
977 int numUnflaggedArguments = 0;
978 for (i = 1; i < argc; i++) {
tomhudson@google.com7d042802011-07-14 13:15:55 +0000979 if (!strcmp(argv[i], "--help")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000980 usage(argv[0]);
981 return 0;
982 }
epoger@google.coma5f406e2012-05-01 13:26:16 +0000983 if (!strcmp(argv[i], "--nodiffs")) {
984 generateDiffs = false;
985 continue;
986 }
987 if (!strcmp(argv[i], "--threshold")) {
988 colorThreshold = atoi(argv[++i]);
989 continue;
990 }
991 if (!strcmp(argv[i], "--match")) {
992 matchSubstrings.push(new SkString(argv[++i]));
993 continue;
994 }
995 if (!strcmp(argv[i], "--nomatch")) {
996 nomatchSubstrings.push(new SkString(argv[++i]));
997 continue;
998 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000999 if (!strcmp(argv[i], "--sortbymismatch")) {
reed@google.comc7a67cb2012-05-07 14:52:12 +00001000 sortProc = SkCastForQSort(compare_diff_mean_mismatches);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001001 continue;
1002 }
tomhudson@google.com7d042802011-07-14 13:15:55 +00001003 if (!strcmp(argv[i], "--sortbymaxmismatch")) {
reed@google.comc7a67cb2012-05-07 14:52:12 +00001004 sortProc = SkCastForQSort(compare_diff_max_mismatches);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001005 continue;
1006 }
tomhudson@google.com7d042802011-07-14 13:15:55 +00001007 if (!strcmp(argv[i], "--weighted")) {
reed@google.comc7a67cb2012-05-07 14:52:12 +00001008 sortProc = SkCastForQSort(compare_diff_weighted);
tomhudson@google.com5b325292011-05-24 19:41:13 +00001009 continue;
1010 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001011 if (argv[i][0] != '-') {
epoger@google.coma5f406e2012-05-01 13:26:16 +00001012 switch (numUnflaggedArguments++) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001013 case 0:
1014 baseDir.set(argv[i]);
1015 continue;
1016 case 1:
1017 comparisonDir.set(argv[i]);
1018 continue;
1019 case 2:
1020 outputDir.set(argv[i]);
1021 continue;
1022 default:
epoger@google.coma5f406e2012-05-01 13:26:16 +00001023 SkDebugf("extra unflagged argument <%s>\n", argv[i]);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001024 usage(argv[0]);
1025 return 0;
1026 }
1027 }
1028
1029 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
1030 usage(argv[0]);
1031 return 0;
1032 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001033
epoger@google.coma5f406e2012-05-01 13:26:16 +00001034 if (numUnflaggedArguments == 2) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001035 outputDir = comparisonDir;
epoger@google.coma5f406e2012-05-01 13:26:16 +00001036 } else if (numUnflaggedArguments != 3) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001037 usage(argv[0]);
1038 return 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001039 }
1040
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001041 if (!baseDir.endsWith(PATH_DIV_STR)) {
1042 baseDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001043 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001044 printf("baseDir is [%s]\n", baseDir.c_str());
1045
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001046 if (!comparisonDir.endsWith(PATH_DIV_STR)) {
1047 comparisonDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001048 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001049 printf("comparisonDir is [%s]\n", comparisonDir.c_str());
1050
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001051 if (!outputDir.endsWith(PATH_DIV_STR)) {
1052 outputDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001053 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001054 if (generateDiffs) {
1055 printf("writing diffs to outputDir is [%s]\n", outputDir.c_str());
1056 } else {
1057 printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str());
1058 outputDir.set("");
1059 }
1060
1061 // Default substring matching:
1062 // - No matter what, don't match any PDF files.
1063 // We may want to change this later, but for now this maintains the filter
1064 // that get_file_list() used to always apply.
1065 // - If no matchSubstrings were specified, match ALL strings.
1066 nomatchSubstrings.push(new SkString(".pdf"));
1067 if (matchSubstrings.isEmpty()) {
1068 matchSubstrings.push(new SkString(""));
1069 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001070
epoger@google.coma611c3e2012-05-18 20:10:06 +00001071 create_diff_images(diffProc, colorThreshold, &differences,
1072 baseDir, comparisonDir, outputDir,
1073 matchSubstrings, nomatchSubstrings, &summary);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001074 summary.print();
tomhudson@google.com7d042802011-07-14 13:15:55 +00001075
1076 if (differences.count()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +00001077 qsort(differences.begin(), differences.count(),
1078 sizeof(DiffRecord*), sortProc);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001079 }
epoger@google.com66008522012-05-16 17:40:57 +00001080
epoger@google.coma5f406e2012-05-01 13:26:16 +00001081 if (generateDiffs) {
1082 print_diff_page(summary.fNumMatches, colorThreshold, differences,
1083 baseDir, comparisonDir, outputDir);
1084 }
1085
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001086 for (i = 0; i < differences.count(); i++) {
1087 delete differences[i];
1088 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001089 matchSubstrings.deleteAll();
1090 nomatchSubstrings.deleteAll();
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001091}