blob: e7d30d256d725594946cb190d953e2301ee830f1 [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.
tomhudson@google.com7d042802011-07-14 13:15:55 +000029 *
30 * With the --chromium flag, *does* recursively descend the first directory
31 * named, comparing *-expected.png with *-actual.png and writing diff
32 * images into the second directory, also writing index.html there.
tomhudson@google.com4b33d282011-04-27 15:39:30 +000033 */
34
bsalomon@google.com1a315fe2011-09-23 14:56:37 +000035#if SK_BUILD_FOR_WIN32
36 #define PATH_DIV_STR "\\"
37 #define PATH_DIV_CHAR '\\'
38#else
39 #define PATH_DIV_STR "/"
40 #define PATH_DIV_CHAR '/'
41#endif
42
epoger@google.com292aff62012-05-16 14:57:28 +000043// Result of comparison for each pair of files.
44// TODO: we don't actually use all of these yet.
45enum Result {
46 kEqualBits, // both files in the pair contain exactly the same bits
47 kEqualPixels, // not bitwise equal, but their pixels are exactly the same
48 kDifferentSizes, // both are images we can parse, but of different sizes
49 kDifferentPixels,// both are images we can parse, but with different pixels
50 kDifferentOther, // files have different bits but are not parsable images
51 kBaseMissing, // missing from baseDir
52 kComparisonMissing,// missing from comparisonDir
53 kUnknown
54};
55
tomhudson@google.com4b33d282011-04-27 15:39:30 +000056struct DiffRecord {
tomhudson@google.com4e305982011-07-13 17:42:46 +000057 DiffRecord (const SkString filename,
58 const SkString basePath,
epoger@google.com5fd53852012-03-22 18:20:06 +000059 const SkString comparisonPath,
epoger@google.com292aff62012-05-16 14:57:28 +000060 const Result result = kUnknown)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000061 : fFilename (filename)
tomhudson@google.com4e305982011-07-13 17:42:46 +000062 , fBasePath (basePath)
63 , fComparisonPath (comparisonPath)
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000064 , fBaseBitmap (new SkBitmap ())
65 , fComparisonBitmap (new SkBitmap ())
66 , fDifferenceBitmap (new SkBitmap ())
epoger@google.com25d961c2012-02-02 20:50:36 +000067 , fWhiteBitmap (new SkBitmap ())
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000068 , fBaseHeight (0)
69 , fBaseWidth (0)
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000070 , fFractionDifference (0)
71 , fWeightedFraction (0)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000072 , fAverageMismatchR (0)
73 , fAverageMismatchG (0)
74 , fAverageMismatchB (0)
75 , fMaxMismatchR (0)
76 , fMaxMismatchG (0)
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +000077 , fMaxMismatchB (0)
epoger@google.com292aff62012-05-16 14:57:28 +000078 , fResult(result) {
tomhudson@google.com7d042802011-07-14 13:15:55 +000079 // These asserts are valid for GM, but not for --chromium
80 //SkASSERT(basePath.endsWith(filename.c_str()));
81 //SkASSERT(comparisonPath.endsWith(filename.c_str()));
tomhudson@google.com4e305982011-07-13 17:42:46 +000082 };
tomhudson@google.com4b33d282011-04-27 15:39:30 +000083
84 SkString fFilename;
tomhudson@google.com4e305982011-07-13 17:42:46 +000085 SkString fBasePath;
86 SkString fComparisonPath;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000087
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000088 SkBitmap* fBaseBitmap;
89 SkBitmap* fComparisonBitmap;
90 SkBitmap* fDifferenceBitmap;
epoger@google.com25d961c2012-02-02 20:50:36 +000091 SkBitmap* fWhiteBitmap;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000092
93 int fBaseHeight;
94 int fBaseWidth;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000095
96 /// Arbitrary floating-point metric to be used to sort images from most
97 /// to least different from baseline; values of 0 will be omitted from the
98 /// summary webpage.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000099 float fFractionDifference;
100 float fWeightedFraction;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000101
102 float fAverageMismatchR;
103 float fAverageMismatchG;
104 float fAverageMismatchB;
105
106 uint32_t fMaxMismatchR;
107 uint32_t fMaxMismatchG;
108 uint32_t fMaxMismatchB;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000109
epoger@google.com292aff62012-05-16 14:57:28 +0000110 /// Which category of diff result.
111 Result fResult;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000112};
113
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000114#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
115#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
116
epoger@google.com25d961c2012-02-02 20:50:36 +0000117const SkPMColor PMCOLOR_WHITE = SkPreMultiplyColor(SK_ColorWHITE);
118const SkPMColor PMCOLOR_BLACK = SkPreMultiplyColor(SK_ColorBLACK);
119
epoger@google.coma5f406e2012-05-01 13:26:16 +0000120typedef SkTDArray<SkString*> StringArray;
121typedef StringArray FileArray;
epoger@google.com5fd53852012-03-22 18:20:06 +0000122
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000123struct DiffSummary {
124 DiffSummary ()
125 : fNumMatches (0)
126 , fNumMismatches (0)
127 , fMaxMismatchV (0)
128 , fMaxMismatchPercent (0) { };
129
epoger@google.com5fd53852012-03-22 18:20:06 +0000130 ~DiffSummary() {
131 fBaseMissing.deleteAll();
132 fComparisonMissing.deleteAll();
133 }
134
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000135 uint32_t fNumMatches;
136 uint32_t fNumMismatches;
137 uint32_t fMaxMismatchV;
138 float fMaxMismatchPercent;
139
epoger@google.com5fd53852012-03-22 18:20:06 +0000140 FileArray fBaseMissing;
141 FileArray fComparisonMissing;
142
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000143 void print () {
epoger@google.com5fd53852012-03-22 18:20:06 +0000144 int n = fBaseMissing.count();
145 if (n > 0) {
146 printf("Missing in baseDir:\n");
147 for (int i = 0; i < n; ++i) {
148 printf("\t%s\n", fBaseMissing[i]->c_str());
149 }
150 }
151 n = fComparisonMissing.count();
152 if (n > 0) {
153 printf("Missing in comparisonDir:\n");
154 for (int i = 0; i < n; ++i) {
155 printf("\t%s\n", fComparisonMissing[i]->c_str());
156 }
157 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000158 printf("%d of %d images matched.\n", fNumMatches,
159 fNumMatches + fNumMismatches);
160 if (fNumMismatches > 0) {
161 printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
162 printf("Largest area mismatch was %.2f%% of pixels\n",
163 fMaxMismatchPercent);
164 }
165
166 }
167
168 void add (DiffRecord* drp) {
epoger@google.com292aff62012-05-16 14:57:28 +0000169 // Maintain current (and, I think, incorrect) skdiff behavior:
170 // If we were unable to parse either file in the pair as an image,
171 // treat them as matching.
172 // TODO: Remove this logic and change the results for the better.
173 if (kUnknown == drp->fResult) {
174 drp->fResult = kEqualPixels;
175 }
176
177 switch (drp->fResult) {
178 case kEqualPixels:
179 fNumMatches++;
180 break;
181 case kBaseMissing:
epoger@google.com5fd53852012-03-22 18:20:06 +0000182 fBaseMissing.push(new SkString(drp->fFilename));
183 fNumMismatches++;
epoger@google.com292aff62012-05-16 14:57:28 +0000184 break;
185 case kComparisonMissing:
epoger@google.com5fd53852012-03-22 18:20:06 +0000186 fComparisonMissing.push(new SkString(drp->fFilename));
187 fNumMismatches++;
epoger@google.com292aff62012-05-16 14:57:28 +0000188 break;
189 default:
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000190 fNumMismatches++;
191 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
192 fMaxMismatchPercent = drp->fFractionDifference * 100;
193 }
tomhudson@google.com88a0e052011-06-09 18:54:01 +0000194 uint32_t value = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
195 drp->fMaxMismatchB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000196 if (value > fMaxMismatchV) {
197 fMaxMismatchV = value;
198 }
epoger@google.com292aff62012-05-16 14:57:28 +0000199 break;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000200 }
201 }
202};
203
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000204typedef SkTDArray<DiffRecord*> RecordArray;
205
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000206/// Comparison routine for qsort; sorts by fFractionDifference
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000207/// from largest to smallest.
208static int compare_diff_metrics (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000209 if ((*lhs)->fFractionDifference < (*rhs)->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000210 return 1;
211 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000212 if ((*rhs)->fFractionDifference < (*lhs)->fFractionDifference) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000213 return -1;
214 }
215 return 0;
216}
217
218static int compare_diff_weighted (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000219 if ((*lhs)->fWeightedFraction < (*rhs)->fWeightedFraction) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000220 return 1;
221 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000222 if ((*lhs)->fWeightedFraction > (*rhs)->fWeightedFraction) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000223 return -1;
224 }
225 return 0;
226}
227
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000228/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
229/// from largest to smallest.
230static int compare_diff_mean_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
231 float leftValue = MAX3((*lhs)->fAverageMismatchR,
232 (*lhs)->fAverageMismatchG,
233 (*lhs)->fAverageMismatchB);
234 float rightValue = MAX3((*rhs)->fAverageMismatchR,
235 (*rhs)->fAverageMismatchG,
236 (*rhs)->fAverageMismatchB);
237 if (leftValue < rightValue) {
238 return 1;
239 }
240 if (rightValue < leftValue) {
241 return -1;
242 }
243 return 0;
244}
245
246/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB})
247/// from largest to smallest.
248static int compare_diff_max_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000249 uint32_t leftValue = MAX3((*lhs)->fMaxMismatchR,
250 (*lhs)->fMaxMismatchG,
251 (*lhs)->fMaxMismatchB);
252 uint32_t rightValue = MAX3((*rhs)->fMaxMismatchR,
253 (*rhs)->fMaxMismatchG,
254 (*rhs)->fMaxMismatchB);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000255 if (leftValue < rightValue) {
256 return 1;
257 }
258 if (rightValue < leftValue) {
259 return -1;
260 }
261 return compare_diff_mean_mismatches(lhs, rhs);
262}
263
264
265
266/// Parameterized routine to compute the color of a pixel in a difference image.
267typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
268
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000269static void expand_and_copy (int width, int height, SkBitmap** dest) {
270 SkBitmap* temp = new SkBitmap ();
271 temp->reset();
272 temp->setConfig((*dest)->config(), width, height);
273 temp->allocPixels();
274 (*dest)->copyPixelsTo(temp->getPixels(), temp->getSize(),
275 temp->rowBytes());
276 *dest = temp;
277}
278
tomhudson@google.com4e305982011-07-13 17:42:46 +0000279static bool get_bitmaps (DiffRecord* diffRecord) {
280 SkFILEStream compareStream(diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000281 if (!compareStream.isValid()) {
282 SkDebugf("WARNING: couldn't open comparison file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000283 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000284 return false;
285 }
286
tomhudson@google.com4e305982011-07-13 17:42:46 +0000287 SkFILEStream baseStream(diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000288 if (!baseStream.isValid()) {
289 SkDebugf("ERROR: couldn't open base file <%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
294 SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
295 if (NULL == codec) {
296 SkDebugf("ERROR: no codec found for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000297 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000298 return false;
299 }
300
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000301 // In debug, the DLL will automatically be unloaded when this is deleted,
302 // but that shouldn't be a problem in release mode.
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000303 SkAutoTDelete<SkImageDecoder> ad(codec);
304
305 baseStream.rewind();
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000306 if (!codec->decode(&baseStream, diffRecord->fBaseBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000307 SkBitmap::kARGB_8888_Config,
308 SkImageDecoder::kDecodePixels_Mode)) {
309 SkDebugf("ERROR: codec failed for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000310 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000311 return false;
312 }
313
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000314 diffRecord->fBaseWidth = diffRecord->fBaseBitmap->width();
315 diffRecord->fBaseHeight = diffRecord->fBaseBitmap->height();
316
317 if (!codec->decode(&compareStream, diffRecord->fComparisonBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000318 SkBitmap::kARGB_8888_Config,
319 SkImageDecoder::kDecodePixels_Mode)) {
320 SkDebugf("ERROR: codec failed for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000321 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000322 return false;
323 }
324
325 return true;
326}
327
epoger@google.com5fd53852012-03-22 18:20:06 +0000328static bool get_bitmap_height_width(const SkString& path,
329 int *height, int *width) {
330 SkFILEStream stream(path.c_str());
331 if (!stream.isValid()) {
332 SkDebugf("ERROR: couldn't open file <%s>\n",
333 path.c_str());
334 return false;
335 }
336
337 SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
338 if (NULL == codec) {
339 SkDebugf("ERROR: no codec found for <%s>\n",
340 path.c_str());
341 return false;
342 }
343
344 SkAutoTDelete<SkImageDecoder> ad(codec);
345 SkBitmap bm;
346
347 stream.rewind();
348 if (!codec->decode(&stream, &bm,
349 SkBitmap::kARGB_8888_Config,
350 SkImageDecoder::kDecodePixels_Mode)) {
351 SkDebugf("ERROR: codec failed for <%s>\n",
352 path.c_str());
353 return false;
354 }
355
356 *height = bm.height();
357 *width = bm.width();
358
359 return true;
360}
361
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000362// from gm - thanks to PNG, we need to force all pixels 100% opaque
363static void force_all_opaque(const SkBitmap& bitmap) {
364 SkAutoLockPixels lock(bitmap);
365 for (int y = 0; y < bitmap.height(); y++) {
366 for (int x = 0; x < bitmap.width(); x++) {
367 *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
368 }
369 }
370}
371
372// from gm
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000373static bool write_bitmap(const SkString& path, const SkBitmap* bitmap) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000374 SkBitmap copy;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000375 bitmap->copyTo(&copy, SkBitmap::kARGB_8888_Config);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000376 force_all_opaque(copy);
377 return SkImageEncoder::EncodeFile(path.c_str(), copy,
378 SkImageEncoder::kPNG_Type, 100);
379}
380
381// from gm
382static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
383 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
384 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
385 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
386
387 return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
388}
389
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000390static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1,
391 const int threshold) {
392 int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
393 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
394 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
395 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
396
397 return ((SkAbs32(da) <= threshold) &&
398 (SkAbs32(dr) <= threshold) &&
399 (SkAbs32(dg) <= threshold) &&
400 (SkAbs32(db) <= threshold));
401}
402
403// based on gm
epoger@google.com66008522012-05-16 17:40:57 +0000404// Postcondition: when we exit this method, dr->fResult should have some value
405// other than kUnknown.
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000406static void compute_diff(DiffRecord* dr,
407 DiffMetricProc diffFunction,
408 const int colorThreshold) {
epoger@google.com25d961c2012-02-02 20:50:36 +0000409 SkAutoLockPixels alpDiff(*dr->fDifferenceBitmap);
410 SkAutoLockPixels alpWhite(*dr->fWhiteBitmap);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000411
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000412 const int w = dr->fComparisonBitmap->width();
413 const int h = dr->fComparisonBitmap->height();
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000414 int mismatchedPixels = 0;
415 int totalMismatchR = 0;
416 int totalMismatchG = 0;
417 int totalMismatchB = 0;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000418
419 if (w != dr->fBaseWidth || h != dr->fBaseHeight) {
epoger@google.com292aff62012-05-16 14:57:28 +0000420 dr->fResult = kDifferentSizes;
421 dr->fFractionDifference = 1; // for sorting the diffs later
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000422 return;
423 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000424 // Accumulate fractionally different pixels, then divide out
425 // # of pixels at the end.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000426 dr->fWeightedFraction = 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000427 for (int y = 0; y < h; y++) {
428 for (int x = 0; x < w; x++) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000429 SkPMColor c0 = *dr->fBaseBitmap->getAddr32(x, y);
430 SkPMColor c1 = *dr->fComparisonBitmap->getAddr32(x, y);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000431 SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
432 SkPMColor outputDifference = diffFunction(c0, c1);
433 uint32_t thisR = SkGetPackedR32(trueDifference);
434 uint32_t thisG = SkGetPackedG32(trueDifference);
435 uint32_t thisB = SkGetPackedB32(trueDifference);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000436 totalMismatchR += thisR;
437 totalMismatchG += thisG;
438 totalMismatchB += thisB;
439 // In HSV, value is defined as max RGB component.
440 int value = MAX3(thisR, thisG, thisB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000441 dr->fWeightedFraction += ((float) value) / 255;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000442 if (thisR > dr->fMaxMismatchR) {
443 dr->fMaxMismatchR = thisR;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000444 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000445 if (thisG > dr->fMaxMismatchG) {
446 dr->fMaxMismatchG = thisG;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000447 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000448 if (thisB > dr->fMaxMismatchB) {
449 dr->fMaxMismatchB = thisB;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000450 }
451 if (!colors_match_thresholded(c0, c1, colorThreshold)) {
452 mismatchedPixels++;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000453 *dr->fDifferenceBitmap->getAddr32(x, y) = outputDifference;
epoger@google.com25d961c2012-02-02 20:50:36 +0000454 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_WHITE;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000455 } else {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000456 *dr->fDifferenceBitmap->getAddr32(x, y) = 0;
epoger@google.com25d961c2012-02-02 20:50:36 +0000457 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_BLACK;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000458 }
459 }
460 }
epoger@google.com292aff62012-05-16 14:57:28 +0000461 if (0 == mismatchedPixels) {
462 dr->fResult = kEqualPixels;
463 return;
464 }
465 dr->fResult = kDifferentPixels;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000466 int pixelCount = w * h;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000467 dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
468 dr->fWeightedFraction /= pixelCount;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000469 dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
470 dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
471 dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000472}
473
epoger@google.com25d961c2012-02-02 20:50:36 +0000474static SkString filename_to_derived_filename (const SkString& filename,
475 const char *suffix) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000476 SkString diffName (filename);
477 const char* cstring = diffName.c_str();
478 int dotOffset = strrchr(cstring, '.') - cstring;
479 diffName.remove(dotOffset, diffName.size() - dotOffset);
epoger@google.com25d961c2012-02-02 20:50:36 +0000480 diffName.append(suffix);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000481 return diffName;
482}
483
epoger@google.com25d961c2012-02-02 20:50:36 +0000484/// Given a image filename, returns the name of the file containing the
485/// associated difference image.
486static SkString filename_to_diff_filename (const SkString& filename) {
487 return filename_to_derived_filename(filename, "-diff.png");
488}
489
490/// Given a image filename, returns the name of the file containing the
491/// "white" difference image.
492static SkString filename_to_white_filename (const SkString& filename) {
493 return filename_to_derived_filename(filename, "-white.png");
494}
495
tomhudson@google.com7d042802011-07-14 13:15:55 +0000496/// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo-actual.png"
497static SkString chrome_expected_path_to_actual (const SkString& expected) {
498 SkString actualPath (expected);
499 actualPath.remove(actualPath.size() - 13, 13);
500 actualPath.append("-actual.png");
501 return actualPath;
502}
503
504/// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo.png"
505static SkString chrome_expected_name_to_short (const SkString& expected) {
506 SkString shortName (expected);
507 shortName.remove(shortName.size() - 13, 13);
508 shortName.append(".png");
509 return shortName;
510}
511
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000512static void release_bitmaps(DiffRecord* drp) {
513 delete drp->fBaseBitmap;
514 drp->fBaseBitmap = NULL;
515 delete drp->fComparisonBitmap;
516 drp->fComparisonBitmap = NULL;
517 delete drp->fDifferenceBitmap;
518 drp->fDifferenceBitmap = NULL;
epoger@google.com25d961c2012-02-02 20:50:36 +0000519 delete drp->fWhiteBitmap;
520 drp->fWhiteBitmap = NULL;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000521}
522
tomhudson@google.com7d042802011-07-14 13:15:55 +0000523
epoger@google.coma5f406e2012-05-01 13:26:16 +0000524/// If outputDir.isEmpty(), don't write out diff files.
tomhudson@google.com7d042802011-07-14 13:15:55 +0000525static void create_and_write_diff_image(DiffRecord* drp,
526 DiffMetricProc dmp,
527 const int colorThreshold,
528 const SkString& outputDir,
529 const SkString& filename) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000530 const int w = drp->fBaseWidth;
531 const int h = drp->fBaseHeight;
532 drp->fDifferenceBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
533 drp->fDifferenceBitmap->allocPixels();
epoger@google.com25d961c2012-02-02 20:50:36 +0000534 drp->fWhiteBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
535 drp->fWhiteBitmap->allocPixels();
tomhudson@google.com7d042802011-07-14 13:15:55 +0000536
epoger@google.com66008522012-05-16 17:40:57 +0000537 SkASSERT(kUnknown == drp->fResult);
538 compute_diff(drp, dmp, colorThreshold);
539 SkASSERT(kUnknown != drp->fResult);
540
541 if ((kDifferentPixels == drp->fResult) && !outputDir.isEmpty()) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000542 SkString differencePath (outputDir);
543 differencePath.append(filename_to_diff_filename(filename));
544 write_bitmap(differencePath, drp->fDifferenceBitmap);
545 SkString whitePath (outputDir);
546 whitePath.append(filename_to_white_filename(filename));
547 write_bitmap(whitePath, drp->fWhiteBitmap);
548 }
epoger@google.com66008522012-05-16 17:40:57 +0000549
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000550 release_bitmaps(drp);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000551}
552
epoger@google.coma5f406e2012-05-01 13:26:16 +0000553/// Returns true if string contains any of these substrings.
554static bool string_contains_any_of(const SkString& string,
555 const StringArray& substrings) {
556 for (int i = 0; i < substrings.count(); i++) {
557 if (string.contains(substrings[i]->c_str())) {
558 return true;
559 }
560 }
561 return false;
562}
563
564/// Iterate over dir and get all files that:
565/// - match any of the substrings in matchSubstrings, but...
566/// - DO NOT match any of the substrings in nomatchSubstrings
567/// Returns the list of files in *files.
568static void get_file_list(const SkString& dir,
569 const StringArray& matchSubstrings,
570 const StringArray& nomatchSubstrings,
571 FileArray *files) {
epoger@google.com5fd53852012-03-22 18:20:06 +0000572 SkOSFile::Iter it(dir.c_str());
573 SkString filename;
574 while (it.next(&filename)) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000575 if (string_contains_any_of(filename, matchSubstrings) &&
576 !string_contains_any_of(filename, nomatchSubstrings)) {
577 files->push(new SkString(filename));
epoger@google.com5fd53852012-03-22 18:20:06 +0000578 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000579 }
580}
581
582static void release_file_list(FileArray *files) {
583 files->deleteAll();
584}
585
586/// Comparison routines for qsort, sort by file names.
587static int compare_file_name_metrics(SkString **lhs, SkString **rhs) {
588 return strcmp((*lhs)->c_str(), (*rhs)->c_str());
589}
590
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000591/// Creates difference images, returns the number that have a 0 metric.
epoger@google.coma5f406e2012-05-01 13:26:16 +0000592/// If outputDir.isEmpty(), don't write out diff files.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000593static void create_diff_images (DiffMetricProc dmp,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000594 const int colorThreshold,
595 RecordArray* differences,
596 const SkString& baseDir,
597 const SkString& comparisonDir,
598 const SkString& outputDir,
epoger@google.coma5f406e2012-05-01 13:26:16 +0000599 const StringArray& matchSubstrings,
600 const StringArray& nomatchSubstrings,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000601 DiffSummary* summary) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000602 SkASSERT(!baseDir.isEmpty());
603 SkASSERT(!comparisonDir.isEmpty());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000604
epoger@google.com5fd53852012-03-22 18:20:06 +0000605 FileArray baseFiles;
606 FileArray comparisonFiles;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000607
epoger@google.coma5f406e2012-05-01 13:26:16 +0000608 get_file_list(baseDir, matchSubstrings, nomatchSubstrings, &baseFiles);
609 get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings,
610 &comparisonFiles);
epoger@google.com5fd53852012-03-22 18:20:06 +0000611
epoger@google.coma5f406e2012-05-01 13:26:16 +0000612 if (!baseFiles.isEmpty()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +0000613 qsort(baseFiles.begin(), baseFiles.count(), sizeof(SkString*),
614 SkCastForQSort(compare_file_name_metrics));
epoger@google.coma5f406e2012-05-01 13:26:16 +0000615 }
616 if (!comparisonFiles.isEmpty()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +0000617 qsort(comparisonFiles.begin(), comparisonFiles.count(),
618 sizeof(SkString*), SkCastForQSort(compare_file_name_metrics));
epoger@google.coma5f406e2012-05-01 13:26:16 +0000619 }
epoger@google.com66008522012-05-16 17:40:57 +0000620
epoger@google.com5fd53852012-03-22 18:20:06 +0000621 int i = 0;
622 int j = 0;
623
624 while (i < baseFiles.count() &&
625 j < comparisonFiles.count()) {
626
tomhudson@google.com4e305982011-07-13 17:42:46 +0000627 SkString basePath (baseDir);
epoger@google.com5fd53852012-03-22 18:20:06 +0000628 basePath.append(*baseFiles[i]);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000629 SkString comparisonPath (comparisonDir);
epoger@google.com5fd53852012-03-22 18:20:06 +0000630 comparisonPath.append(*comparisonFiles[j]);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000631
epoger@google.com5fd53852012-03-22 18:20:06 +0000632 DiffRecord *drp = NULL;
633 int v = strcmp(baseFiles[i]->c_str(),
634 comparisonFiles[j]->c_str());
635
636 if (v < 0) {
637 // in baseDir, but not in comparisonDir
epoger@google.com292aff62012-05-16 14:57:28 +0000638 drp = new DiffRecord(*baseFiles[i], basePath, comparisonPath,
639 kComparisonMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000640 ++i;
641 } else if (v > 0) {
642 // in comparisonDir, but not in baseDir
epoger@google.com292aff62012-05-16 14:57:28 +0000643 drp = new DiffRecord(*comparisonFiles[j], basePath, comparisonPath,
644 kBaseMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000645 ++j;
646 } else {
647 // let's diff!
648 drp = new DiffRecord(*baseFiles[i], basePath, comparisonPath);
649
650 if (get_bitmaps(drp)) {
651 create_and_write_diff_image(drp, dmp, colorThreshold,
652 outputDir, *baseFiles[i]);
653 }
654
655 ++i;
656 ++j;
657 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000658
659 differences->push(drp);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000660 summary->add(drp);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000661 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000662
663 for (; i < baseFiles.count(); ++i) {
664 // files only in baseDir
665 SkString basePath (baseDir);
666 basePath.append(*baseFiles[i]);
667 SkString comparisonPath;
668 DiffRecord *drp = new DiffRecord(*baseFiles[i], basePath,
epoger@google.com292aff62012-05-16 14:57:28 +0000669 comparisonPath, kComparisonMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000670 differences->push(drp);
671 summary->add(drp);
672 }
673
674 for (; j < comparisonFiles.count(); ++j) {
675 // files only in comparisonDir
676 SkString basePath;
677 SkString comparisonPath(comparisonDir);
678 comparisonPath.append(*comparisonFiles[j]);
679 DiffRecord *drp = new DiffRecord(*comparisonFiles[j], basePath,
epoger@google.com292aff62012-05-16 14:57:28 +0000680 comparisonPath, kBaseMissing);
epoger@google.com5fd53852012-03-22 18:20:06 +0000681 differences->push(drp);
682 summary->add(drp);
683 }
684
685 release_file_list(&baseFiles);
686 release_file_list(&comparisonFiles);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000687}
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000688
tomhudson@google.com7d042802011-07-14 13:15:55 +0000689static void create_diff_images_chromium (DiffMetricProc dmp,
690 const int colorThreshold,
691 RecordArray* differences,
692 const SkString& dirname,
693 const SkString& outputDir,
694 DiffSummary* summary) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000695 SkASSERT(!outputDir.isEmpty());
696
tomhudson@google.com7d042802011-07-14 13:15:55 +0000697 SkOSFile::Iter baseIterator (dirname.c_str());
698 SkString filename;
699 while (baseIterator.next(&filename)) {
700 if (filename.endsWith(".pdf")) {
701 continue;
702 }
703 if (filename.endsWith("-expected.png")) {
704 SkString expectedPath (dirname);
705 expectedPath.append(filename);
706 SkString shortName (chrome_expected_name_to_short(filename));
707 SkString actualPath (chrome_expected_path_to_actual(expectedPath));
708 DiffRecord * drp =
709 new DiffRecord (shortName, expectedPath, actualPath);
710 if (!get_bitmaps(drp)) {
711 continue;
712 }
713 create_and_write_diff_image(drp, dmp, colorThreshold,
714 outputDir, shortName);
715
716 differences->push(drp);
717 summary->add(drp);
718 }
719 }
720}
721
722static void analyze_chromium(DiffMetricProc dmp,
723 const int colorThreshold,
724 RecordArray* differences,
725 const SkString& dirname,
726 const SkString& outputDir,
727 DiffSummary* summary) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000728 SkASSERT(!outputDir.isEmpty());
tomhudson@google.com7d042802011-07-14 13:15:55 +0000729 create_diff_images_chromium(dmp, colorThreshold, differences,
730 dirname, outputDir, summary);
731 SkOSFile::Iter dirIterator(dirname.c_str());
732 SkString newdirname;
733 while (dirIterator.next(&newdirname, true)) {
734 if (newdirname.startsWith(".")) {
735 continue;
736 }
737 SkString fullname (dirname);
738 fullname.append(newdirname);
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000739 if (!fullname.endsWith(PATH_DIV_STR)) {
740 fullname.append(PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000741 }
742 analyze_chromium(dmp, colorThreshold, differences,
743 fullname, outputDir, summary);
744 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000745}
746
747/// Make layout more consistent by scaling image to 240 height, 360 width,
748/// or natural size, whichever is smallest.
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000749static int compute_image_height (int height, int width) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000750 int retval = 240;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000751 if (height < retval) {
752 retval = height;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000753 }
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000754 float scale = (float) retval / height;
755 if (width * scale > 360) {
756 scale = (float) 360 / width;
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000757 retval = static_cast<int>(height * scale);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000758 }
759 return retval;
760}
761
epoger@google.com25d961c2012-02-02 20:50:36 +0000762static void print_table_header (SkFILEWStream* stream,
763 const int matchCount,
764 const int colorThreshold,
765 const RecordArray& differences,
766 const SkString &baseDir,
epoger@google.coma2b793c2012-05-15 14:58:53 +0000767 const SkString &comparisonDir,
768 bool doOutputDate=false) {
epoger@google.com25d961c2012-02-02 20:50:36 +0000769 stream->writeText("<table>\n");
770 stream->writeText("<tr><th>");
epoger@google.coma2b793c2012-05-15 14:58:53 +0000771 if (doOutputDate) {
772 SkTime::DateTime dt;
773 SkTime::GetDateTime(&dt);
774 stream->writeText("SkDiff run at ");
775 stream->writeDecAsText(dt.fHour);
776 stream->writeText(":");
777 if (dt.fMinute < 10) {
778 stream->writeText("0");
779 }
780 stream->writeDecAsText(dt.fMinute);
781 stream->writeText(":");
782 if (dt.fSecond < 10) {
783 stream->writeText("0");
784 }
785 stream->writeDecAsText(dt.fSecond);
786 stream->writeText("<br>");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000787 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000788 stream->writeDecAsText(matchCount);
789 stream->writeText(" of ");
790 stream->writeDecAsText(differences.count());
791 stream->writeText(" images matched ");
792 if (colorThreshold == 0) {
793 stream->writeText("exactly");
794 } else {
795 stream->writeText("within ");
796 stream->writeDecAsText(colorThreshold);
797 stream->writeText(" color units per component");
798 }
799 stream->writeText(".<br>");
epoger@google.com25d961c2012-02-02 20:50:36 +0000800 stream->writeText("</th>\n<th>");
801 stream->writeText("every different pixel shown in white");
802 stream->writeText("</th>\n<th>");
803 stream->writeText("color difference at each pixel");
804 stream->writeText("</th>\n<th>");
805 stream->writeText(baseDir.c_str());
806 stream->writeText("</th>\n<th>");
807 stream->writeText(comparisonDir.c_str());
808 stream->writeText("</th>\n");
809 stream->writeText("</tr>\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000810}
811
812static void print_pixel_count (SkFILEWStream* stream,
813 const DiffRecord& diff) {
814 stream->writeText("<br>(");
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000815 stream->writeDecAsText(static_cast<int>(diff.fFractionDifference *
816 diff.fBaseWidth *
817 diff.fBaseHeight));
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000818 stream->writeText(" pixels)");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000819/*
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000820 stream->writeDecAsText(diff.fWeightedFraction *
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000821 diff.fBaseWidth *
822 diff.fBaseHeight);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000823 stream->writeText(" weighted pixels)");
824*/
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000825}
826
827static void print_label_cell (SkFILEWStream* stream,
828 const DiffRecord& diff) {
829 stream->writeText("<td>");
830 stream->writeText(diff.fFilename.c_str());
831 stream->writeText("<br>");
epoger@google.com292aff62012-05-16 14:57:28 +0000832 switch (diff.fResult) {
833 case kBaseMissing:
834 // fall through
835 case kComparisonMissing:
epoger@google.com5fd53852012-03-22 18:20:06 +0000836 stream->writeText("</td>");
837 return;
epoger@google.com292aff62012-05-16 14:57:28 +0000838 case kDifferentSizes:
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000839 stream->writeText("Image sizes differ");
840 stream->writeText("</td>");
841 return;
epoger@google.com292aff62012-05-16 14:57:28 +0000842 default:
843 break; // continue in this function
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000844 }
epoger@google.com292aff62012-05-16 14:57:28 +0000845
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000846 char metricBuf [20];
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000847 sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000848 stream->writeText(metricBuf);
849 stream->writeText(" of pixels differ");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000850 stream->writeText("\n (");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000851 sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000852 stream->writeText(metricBuf);
853 stream->writeText(" weighted)");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000854 // Write the actual number of pixels that differ if it's < 1%
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000855 if (diff.fFractionDifference < 0.01) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000856 print_pixel_count(stream, diff);
857 }
858 stream->writeText("<br>Average color mismatch ");
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000859 stream->writeDecAsText(static_cast<int>(MAX3(diff.fAverageMismatchR,
860 diff.fAverageMismatchG,
861 diff.fAverageMismatchB)));
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000862 stream->writeText("<br>Max color mismatch ");
863 stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
864 diff.fMaxMismatchG,
865 diff.fMaxMismatchB));
866 stream->writeText("</td>");
867}
868
869static void print_image_cell (SkFILEWStream* stream,
tomhudson@google.com4e305982011-07-13 17:42:46 +0000870 const SkString& path,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000871 int height) {
872 stream->writeText("<td><a href=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000873 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000874 stream->writeText("\"><img src=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000875 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000876 stream->writeText("\" height=\"");
877 stream->writeDecAsText(height);
878 stream->writeText("px\"></a></td>");
879}
880
epoger@google.com01f78702012-04-12 16:32:04 +0000881static void print_text_cell (SkFILEWStream* stream, const char* text) {
882 stream->writeText("<td align=center>");
883 if (NULL != text) {
884 stream->writeText(text);
885 }
886 stream->writeText("</td>");
887}
888
epoger@google.com5fd53852012-03-22 18:20:06 +0000889static void print_diff_with_missing_file(SkFILEWStream* stream,
890 DiffRecord& diff,
891 const SkString& relativePath) {
892 stream->writeText("<tr>\n");
893 print_label_cell(stream, diff);
894 stream->writeText("<td>N/A</td>");
895 stream->writeText("<td>N/A</td>");
epoger@google.com292aff62012-05-16 14:57:28 +0000896 if (kBaseMissing != diff.fResult) {
epoger@google.com5fd53852012-03-22 18:20:06 +0000897 int h, w;
898 if (!get_bitmap_height_width(diff.fBasePath, &h, &w)) {
899 stream->writeText("<td>N/A</td>");
900 } else {
901 int height = compute_image_height(h, w);
902 if (!diff.fBasePath.startsWith(PATH_DIV_STR)) {
903 diff.fBasePath.prepend(relativePath);
904 }
905 print_image_cell(stream, diff.fBasePath, height);
906 }
907 } else {
908 stream->writeText("<td>N/A</td>");
909 }
epoger@google.com292aff62012-05-16 14:57:28 +0000910 if (kComparisonMissing != diff.fResult) {
epoger@google.com5fd53852012-03-22 18:20:06 +0000911 int h, w;
912 if (!get_bitmap_height_width(diff.fComparisonPath, &h, &w)) {
913 stream->writeText("<td>N/A</td>");
914 } else {
915 int height = compute_image_height(h, w);
916 if (!diff.fComparisonPath.startsWith(PATH_DIV_STR)) {
917 diff.fComparisonPath.prepend(relativePath);
918 }
919 print_image_cell(stream, diff.fComparisonPath, height);
920 }
921 } else {
922 stream->writeText("<td>N/A</td>");
923 }
924 stream->writeText("</tr>\n");
925 stream->flush();
926}
927
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000928static void print_diff_page (const int matchCount,
929 const int colorThreshold,
930 const RecordArray& differences,
931 const SkString& baseDir,
932 const SkString& comparisonDir,
933 const SkString& outputDir) {
934
epoger@google.coma5f406e2012-05-01 13:26:16 +0000935 SkASSERT(!baseDir.isEmpty());
936 SkASSERT(!comparisonDir.isEmpty());
937 SkASSERT(!outputDir.isEmpty());
938
tomhudson@google.com5b325292011-05-24 19:41:13 +0000939 SkString outputPath (outputDir);
940 outputPath.append("index.html");
941 //SkFILEWStream outputStream ("index.html");
942 SkFILEWStream outputStream (outputPath.c_str());
943
tomhudson@google.com4e305982011-07-13 17:42:46 +0000944 // Need to convert paths from relative-to-cwd to relative-to-outputDir
tomhudson@google.com5b325292011-05-24 19:41:13 +0000945 // FIXME this doesn't work if there are '..' inside the outputDir
946 unsigned int ui;
947 SkString relativePath;
948 for (ui = 0; ui < outputDir.size(); ui++) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000949 if (outputDir[ui] == PATH_DIV_CHAR) {
950 relativePath.append(".." PATH_DIV_STR);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000951 }
952 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000953
954 outputStream.writeText("<html>\n<body>\n");
epoger@google.com25d961c2012-02-02 20:50:36 +0000955 print_table_header(&outputStream, matchCount, colorThreshold, differences,
956 baseDir, comparisonDir);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000957 int i;
958 for (i = 0; i < differences.count(); i++) {
959 DiffRecord* diff = differences[i];
epoger@google.com5fd53852012-03-22 18:20:06 +0000960
epoger@google.com292aff62012-05-16 14:57:28 +0000961 switch (diff->fResult) {
962 case kEqualPixels:
963 continue;
964 case kBaseMissing:
965 // fall through
966 case kComparisonMissing:
epoger@google.com5fd53852012-03-22 18:20:06 +0000967 print_diff_with_missing_file(&outputStream, *diff, relativePath);
968 continue;
epoger@google.com292aff62012-05-16 14:57:28 +0000969 default:
970 break;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000971 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000972
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000973 if (!diff->fBasePath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000974 diff->fBasePath.prepend(relativePath);
975 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000976 if (!diff->fComparisonPath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000977 diff->fComparisonPath.prepend(relativePath);
978 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000979
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000980 int height = compute_image_height(diff->fBaseHeight, diff->fBaseWidth);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000981 outputStream.writeText("<tr>\n");
982 print_label_cell(&outputStream, *diff);
epoger@google.com292aff62012-05-16 14:57:28 +0000983 switch (diff->fResult) {
984 case kDifferentSizes:
epoger@google.com01f78702012-04-12 16:32:04 +0000985 print_text_cell(&outputStream,
986 "[image size mismatch, so no diff to display]");
987 print_text_cell(&outputStream,
988 "[image size mismatch, so no diff to display]");
epoger@google.com292aff62012-05-16 14:57:28 +0000989 break;
990 default:
epoger@google.com01f78702012-04-12 16:32:04 +0000991 print_image_cell(&outputStream,
992 filename_to_white_filename(diff->fFilename), height);
993 print_image_cell(&outputStream,
994 filename_to_diff_filename(diff->fFilename), height);
995 }
epoger@google.com25d961c2012-02-02 20:50:36 +0000996 print_image_cell(&outputStream, diff->fBasePath, height);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000997 print_image_cell(&outputStream, diff->fComparisonPath, height);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000998 outputStream.writeText("</tr>\n");
999 outputStream.flush();
1000 }
1001 outputStream.writeText("</table>\n");
1002 outputStream.writeText("</body>\n</html>\n");
1003 outputStream.flush();
1004}
1005
1006static void usage (char * argv0) {
1007 SkDebugf("Skia baseline image diff tool\n");
epoger@google.coma5f406e2012-05-01 13:26:16 +00001008 SkDebugf("\n"
1009"Usage: \n"
1010" %s <baseDir> <comparisonDir> [outputDir] \n"
1011"or \n"
1012" %s --chromium-release|--chromium-debug <baseDir> <outputDir> \n",
1013argv0, argv0);
1014 SkDebugf("\n"
1015"Arguments: \n"
1016" --nodiffs: don't write out image diffs or index.html, just generate \n"
1017" report on stdout \n"
1018" --threshold <n>: only report differences > n (per color channel) [default 0]\n"
1019" --match: compare files whose filenames contain this substring; if \n"
1020" unspecified, compare ALL files. \n"
1021" this flag may be repeated to add more matching substrings. \n"
1022" --nomatch: regardless of --match, DO NOT compare files whose filenames \n"
1023" contain this substring. \n"
1024" this flag may be repeated to add more forbidden substrings. \n"
tomhudson@google.com7d042802011-07-14 13:15:55 +00001025" --sortbymismatch: sort by average color channel mismatch\n");
1026 SkDebugf(
epoger@google.coma5f406e2012-05-01 13:26:16 +00001027" --sortbymaxmismatch: sort by worst color channel mismatch;\n"
1028" break ties with -sortbymismatch\n"
1029" [default sort is by fraction of pixels mismatching]\n");
tomhudson@google.com5b325292011-05-24 19:41:13 +00001030 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +00001031" --weighted: sort by # pixels different weighted by color difference\n");
1032 SkDebugf(
1033" --chromium-release: process Webkit LayoutTests results instead of gm\n"
1034" --chromium-debug: process Webkit LayoutTests results instead of gm\n");
1035 SkDebugf(
1036" baseDir: directory to read baseline images from,\n"
1037" or chromium/src directory for --chromium.\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001038 SkDebugf(
epoger@google.coma5f406e2012-05-01 13:26:16 +00001039" comparisonDir: directory to read comparison images from\n");
1040 SkDebugf(
1041" outputDir: directory to write difference images and index.html to; \n"
1042" defaults to comparisonDir when not running --chromium \n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001043}
1044
1045int main (int argc, char ** argv) {
1046 DiffMetricProc diffProc = compute_diff_pmcolor;
reed@google.comc7a67cb2012-05-07 14:52:12 +00001047 int (*sortProc)(const void*, const void*) = SkCastForQSort(compare_diff_metrics);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001048
1049 // Maximum error tolerated in any one color channel in any one pixel before
1050 // a difference is reported.
1051 int colorThreshold = 0;
1052 SkString baseDir;
1053 SkString comparisonDir;
1054 SkString outputDir;
epoger@google.coma5f406e2012-05-01 13:26:16 +00001055 StringArray matchSubstrings;
1056 StringArray nomatchSubstrings;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001057
tomhudson@google.com7d042802011-07-14 13:15:55 +00001058 bool analyzeChromium = false;
1059 bool chromiumDebug = false;
1060 bool chromiumRelease = false;
epoger@google.coma5f406e2012-05-01 13:26:16 +00001061 bool generateDiffs = true;
tomhudson@google.com7d042802011-07-14 13:15:55 +00001062
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001063 RecordArray differences;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001064 DiffSummary summary;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001065
epoger@google.coma5f406e2012-05-01 13:26:16 +00001066 int i;
1067 int numUnflaggedArguments = 0;
1068 for (i = 1; i < argc; i++) {
tomhudson@google.com7d042802011-07-14 13:15:55 +00001069 if (!strcmp(argv[i], "--help")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001070 usage(argv[0]);
1071 return 0;
1072 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001073 if (!strcmp(argv[i], "--nodiffs")) {
1074 generateDiffs = false;
1075 continue;
1076 }
1077 if (!strcmp(argv[i], "--threshold")) {
1078 colorThreshold = atoi(argv[++i]);
1079 continue;
1080 }
1081 if (!strcmp(argv[i], "--match")) {
1082 matchSubstrings.push(new SkString(argv[++i]));
1083 continue;
1084 }
1085 if (!strcmp(argv[i], "--nomatch")) {
1086 nomatchSubstrings.push(new SkString(argv[++i]));
1087 continue;
1088 }
tomhudson@google.com7d042802011-07-14 13:15:55 +00001089 if (!strcmp(argv[i], "--sortbymismatch")) {
reed@google.comc7a67cb2012-05-07 14:52:12 +00001090 sortProc = SkCastForQSort(compare_diff_mean_mismatches);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001091 continue;
1092 }
tomhudson@google.com7d042802011-07-14 13:15:55 +00001093 if (!strcmp(argv[i], "--sortbymaxmismatch")) {
reed@google.comc7a67cb2012-05-07 14:52:12 +00001094 sortProc = SkCastForQSort(compare_diff_max_mismatches);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001095 continue;
1096 }
tomhudson@google.com7d042802011-07-14 13:15:55 +00001097 if (!strcmp(argv[i], "--weighted")) {
reed@google.comc7a67cb2012-05-07 14:52:12 +00001098 sortProc = SkCastForQSort(compare_diff_weighted);
tomhudson@google.com5b325292011-05-24 19:41:13 +00001099 continue;
1100 }
tomhudson@google.com7d042802011-07-14 13:15:55 +00001101 if (!strcmp(argv[i], "--chromium-release")) {
1102 analyzeChromium = true;
1103 chromiumRelease = true;
1104 continue;
1105 }
1106 if (!strcmp(argv[i], "--chromium-debug")) {
1107 analyzeChromium = true;
1108 chromiumDebug = true;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001109 continue;
1110 }
1111 if (argv[i][0] != '-') {
epoger@google.coma5f406e2012-05-01 13:26:16 +00001112 switch (numUnflaggedArguments++) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001113 case 0:
1114 baseDir.set(argv[i]);
1115 continue;
1116 case 1:
1117 comparisonDir.set(argv[i]);
1118 continue;
1119 case 2:
1120 outputDir.set(argv[i]);
1121 continue;
1122 default:
epoger@google.coma5f406e2012-05-01 13:26:16 +00001123 SkDebugf("extra unflagged argument <%s>\n", argv[i]);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001124 usage(argv[0]);
1125 return 0;
1126 }
1127 }
1128
1129 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
1130 usage(argv[0]);
1131 return 0;
1132 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001133
tomhudson@google.com7d042802011-07-14 13:15:55 +00001134 if (analyzeChromium) {
epoger@google.coma5f406e2012-05-01 13:26:16 +00001135 if (numUnflaggedArguments != 2) {
tomhudson@google.com7d042802011-07-14 13:15:55 +00001136 usage(argv[0]);
1137 return 0;
1138 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001139 outputDir = comparisonDir;
tomhudson@google.com7d042802011-07-14 13:15:55 +00001140 if (chromiumRelease && chromiumDebug) {
1141 SkDebugf(
1142"--chromium must be either -release or -debug, not both!\n");
1143 return 0;
1144 }
1145 }
epoger@google.com66008522012-05-16 17:40:57 +00001146
epoger@google.coma5f406e2012-05-01 13:26:16 +00001147 if (numUnflaggedArguments == 2) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001148 outputDir = comparisonDir;
epoger@google.coma5f406e2012-05-01 13:26:16 +00001149 } else if (numUnflaggedArguments != 3) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001150 usage(argv[0]);
1151 return 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001152 }
1153
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001154 if (!baseDir.endsWith(PATH_DIV_STR)) {
1155 baseDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001156 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001157 printf("baseDir is [%s]\n", baseDir.c_str());
1158
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001159 if (!comparisonDir.endsWith(PATH_DIV_STR)) {
1160 comparisonDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001161 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001162 printf("comparisonDir is [%s]\n", comparisonDir.c_str());
1163
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001164 if (!outputDir.endsWith(PATH_DIV_STR)) {
1165 outputDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001166 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001167 if (generateDiffs) {
1168 printf("writing diffs to outputDir is [%s]\n", outputDir.c_str());
1169 } else {
1170 printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str());
1171 outputDir.set("");
1172 }
1173
1174 // Default substring matching:
1175 // - No matter what, don't match any PDF files.
1176 // We may want to change this later, but for now this maintains the filter
1177 // that get_file_list() used to always apply.
1178 // - If no matchSubstrings were specified, match ALL strings.
1179 nomatchSubstrings.push(new SkString(".pdf"));
1180 if (matchSubstrings.isEmpty()) {
1181 matchSubstrings.push(new SkString(""));
1182 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001183
tomhudson@google.com7d042802011-07-14 13:15:55 +00001184 if (analyzeChromium) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001185 baseDir.append("webkit" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001186 if (chromiumRelease) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001187 baseDir.append("Release" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001188 }
1189 if (chromiumDebug) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001190 baseDir.append("Debug" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001191 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001192 baseDir.append("layout-test-results" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001193 analyze_chromium(diffProc, colorThreshold, &differences,
1194 baseDir, outputDir, &summary);
1195 } else {
1196 create_diff_images(diffProc, colorThreshold, &differences,
epoger@google.coma5f406e2012-05-01 13:26:16 +00001197 baseDir, comparisonDir, outputDir,
1198 matchSubstrings, nomatchSubstrings, &summary);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001199 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001200 summary.print();
tomhudson@google.com7d042802011-07-14 13:15:55 +00001201
1202 if (differences.count()) {
reed@google.comc7a67cb2012-05-07 14:52:12 +00001203 qsort(differences.begin(), differences.count(),
1204 sizeof(DiffRecord*), sortProc);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001205 }
epoger@google.com66008522012-05-16 17:40:57 +00001206
epoger@google.coma5f406e2012-05-01 13:26:16 +00001207 if (generateDiffs) {
1208 print_diff_page(summary.fNumMatches, colorThreshold, differences,
1209 baseDir, comparisonDir, outputDir);
1210 }
1211
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001212 for (i = 0; i < differences.count(); i++) {
1213 delete differences[i];
1214 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001215 matchSubstrings.deleteAll();
1216 nomatchSubstrings.deleteAll();
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001217}