blob: 6fb3326c0c84b24f20e725e7eb394eebcc384af8 [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
tomhudson@google.com4b33d282011-04-27 15:39:30 +000043struct DiffRecord {
tomhudson@google.com4e305982011-07-13 17:42:46 +000044 DiffRecord (const SkString filename,
45 const SkString basePath,
epoger@google.com5fd53852012-03-22 18:20:06 +000046 const SkString comparisonPath,
47 bool baseMissing = false,
48 bool comparisonMissing = false)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000049 : fFilename (filename)
tomhudson@google.com4e305982011-07-13 17:42:46 +000050 , fBasePath (basePath)
51 , fComparisonPath (comparisonPath)
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000052 , fBaseBitmap (new SkBitmap ())
53 , fComparisonBitmap (new SkBitmap ())
54 , fDifferenceBitmap (new SkBitmap ())
epoger@google.com25d961c2012-02-02 20:50:36 +000055 , fWhiteBitmap (new SkBitmap ())
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000056 , fBaseHeight (0)
57 , fBaseWidth (0)
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000058 , fFractionDifference (0)
59 , fWeightedFraction (0)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000060 , fAverageMismatchR (0)
61 , fAverageMismatchG (0)
62 , fAverageMismatchB (0)
63 , fMaxMismatchR (0)
64 , fMaxMismatchG (0)
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +000065 , fMaxMismatchB (0)
epoger@google.com5fd53852012-03-22 18:20:06 +000066 , fDoImageSizesMismatch (false)
67 , fBaseMissing(baseMissing)
68 , fComparisonMissing(comparisonMissing) {
tomhudson@google.com7d042802011-07-14 13:15:55 +000069 // These asserts are valid for GM, but not for --chromium
70 //SkASSERT(basePath.endsWith(filename.c_str()));
71 //SkASSERT(comparisonPath.endsWith(filename.c_str()));
tomhudson@google.com4e305982011-07-13 17:42:46 +000072 };
tomhudson@google.com4b33d282011-04-27 15:39:30 +000073
74 SkString fFilename;
tomhudson@google.com4e305982011-07-13 17:42:46 +000075 SkString fBasePath;
76 SkString fComparisonPath;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000077
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000078 SkBitmap* fBaseBitmap;
79 SkBitmap* fComparisonBitmap;
80 SkBitmap* fDifferenceBitmap;
epoger@google.com25d961c2012-02-02 20:50:36 +000081 SkBitmap* fWhiteBitmap;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000082
83 int fBaseHeight;
84 int fBaseWidth;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000085
86 /// Arbitrary floating-point metric to be used to sort images from most
87 /// to least different from baseline; values of 0 will be omitted from the
88 /// summary webpage.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000089 float fFractionDifference;
90 float fWeightedFraction;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000091
92 float fAverageMismatchR;
93 float fAverageMismatchG;
94 float fAverageMismatchB;
95
96 uint32_t fMaxMismatchR;
97 uint32_t fMaxMismatchG;
98 uint32_t fMaxMismatchB;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +000099
100 /// By the time we need to report image size mismatch, we've already
101 /// released the bitmaps, so we need to remember it when we detect it.
102 bool fDoImageSizesMismatch;
epoger@google.com5fd53852012-03-22 18:20:06 +0000103
104 bool fBaseMissing;
105 bool fComparisonMissing;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000106};
107
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000108#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
109#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
110
epoger@google.com25d961c2012-02-02 20:50:36 +0000111const SkPMColor PMCOLOR_WHITE = SkPreMultiplyColor(SK_ColorWHITE);
112const SkPMColor PMCOLOR_BLACK = SkPreMultiplyColor(SK_ColorBLACK);
113
epoger@google.coma5f406e2012-05-01 13:26:16 +0000114typedef SkTDArray<SkString*> StringArray;
115typedef StringArray FileArray;
epoger@google.com5fd53852012-03-22 18:20:06 +0000116
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000117struct DiffSummary {
118 DiffSummary ()
119 : fNumMatches (0)
120 , fNumMismatches (0)
121 , fMaxMismatchV (0)
122 , fMaxMismatchPercent (0) { };
123
epoger@google.com5fd53852012-03-22 18:20:06 +0000124 ~DiffSummary() {
125 fBaseMissing.deleteAll();
126 fComparisonMissing.deleteAll();
127 }
128
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000129 uint32_t fNumMatches;
130 uint32_t fNumMismatches;
131 uint32_t fMaxMismatchV;
132 float fMaxMismatchPercent;
133
epoger@google.com5fd53852012-03-22 18:20:06 +0000134 FileArray fBaseMissing;
135 FileArray fComparisonMissing;
136
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000137 void print () {
epoger@google.com5fd53852012-03-22 18:20:06 +0000138 int n = fBaseMissing.count();
139 if (n > 0) {
140 printf("Missing in baseDir:\n");
141 for (int i = 0; i < n; ++i) {
142 printf("\t%s\n", fBaseMissing[i]->c_str());
143 }
144 }
145 n = fComparisonMissing.count();
146 if (n > 0) {
147 printf("Missing in comparisonDir:\n");
148 for (int i = 0; i < n; ++i) {
149 printf("\t%s\n", fComparisonMissing[i]->c_str());
150 }
151 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000152 printf("%d of %d images matched.\n", fNumMatches,
153 fNumMatches + fNumMismatches);
154 if (fNumMismatches > 0) {
155 printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
156 printf("Largest area mismatch was %.2f%% of pixels\n",
157 fMaxMismatchPercent);
158 }
159
160 }
161
162 void add (DiffRecord* drp) {
epoger@google.com5fd53852012-03-22 18:20:06 +0000163 if (drp->fBaseMissing) {
164 fBaseMissing.push(new SkString(drp->fFilename));
165 fNumMismatches++;
166 } else if (drp->fComparisonMissing) {
167 fComparisonMissing.push(new SkString(drp->fFilename));
168 fNumMismatches++;
169 } else if (0 == drp->fFractionDifference) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000170 fNumMatches++;
171 } else {
172 fNumMismatches++;
173 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
174 fMaxMismatchPercent = drp->fFractionDifference * 100;
175 }
tomhudson@google.com88a0e052011-06-09 18:54:01 +0000176 uint32_t value = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
177 drp->fMaxMismatchB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000178 if (value > fMaxMismatchV) {
179 fMaxMismatchV = value;
180 }
181 }
182 }
183};
184
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000185typedef SkTDArray<DiffRecord*> RecordArray;
186
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000187/// Comparison routine for qsort; sorts by fFractionDifference
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000188/// from largest to smallest.
189static int compare_diff_metrics (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000190 if ((*lhs)->fFractionDifference < (*rhs)->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000191 return 1;
192 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000193 if ((*rhs)->fFractionDifference < (*lhs)->fFractionDifference) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000194 return -1;
195 }
196 return 0;
197}
198
199static int compare_diff_weighted (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000200 if ((*lhs)->fWeightedFraction < (*rhs)->fWeightedFraction) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000201 return 1;
202 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000203 if ((*lhs)->fWeightedFraction > (*rhs)->fWeightedFraction) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000204 return -1;
205 }
206 return 0;
207}
208
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000209/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
210/// from largest to smallest.
211static int compare_diff_mean_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
212 float leftValue = MAX3((*lhs)->fAverageMismatchR,
213 (*lhs)->fAverageMismatchG,
214 (*lhs)->fAverageMismatchB);
215 float rightValue = MAX3((*rhs)->fAverageMismatchR,
216 (*rhs)->fAverageMismatchG,
217 (*rhs)->fAverageMismatchB);
218 if (leftValue < rightValue) {
219 return 1;
220 }
221 if (rightValue < leftValue) {
222 return -1;
223 }
224 return 0;
225}
226
227/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB})
228/// from largest to smallest.
229static int compare_diff_max_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000230 uint32_t leftValue = MAX3((*lhs)->fMaxMismatchR,
231 (*lhs)->fMaxMismatchG,
232 (*lhs)->fMaxMismatchB);
233 uint32_t rightValue = MAX3((*rhs)->fMaxMismatchR,
234 (*rhs)->fMaxMismatchG,
235 (*rhs)->fMaxMismatchB);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000236 if (leftValue < rightValue) {
237 return 1;
238 }
239 if (rightValue < leftValue) {
240 return -1;
241 }
242 return compare_diff_mean_mismatches(lhs, rhs);
243}
244
245
246
247/// Parameterized routine to compute the color of a pixel in a difference image.
248typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
249
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000250static void expand_and_copy (int width, int height, SkBitmap** dest) {
251 SkBitmap* temp = new SkBitmap ();
252 temp->reset();
253 temp->setConfig((*dest)->config(), width, height);
254 temp->allocPixels();
255 (*dest)->copyPixelsTo(temp->getPixels(), temp->getSize(),
256 temp->rowBytes());
257 *dest = temp;
258}
259
tomhudson@google.com4e305982011-07-13 17:42:46 +0000260static bool get_bitmaps (DiffRecord* diffRecord) {
261 SkFILEStream compareStream(diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000262 if (!compareStream.isValid()) {
263 SkDebugf("WARNING: couldn't open comparison file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000264 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000265 return false;
266 }
267
tomhudson@google.com4e305982011-07-13 17:42:46 +0000268 SkFILEStream baseStream(diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000269 if (!baseStream.isValid()) {
270 SkDebugf("ERROR: couldn't open base file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000271 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000272 return false;
273 }
274
275 SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
276 if (NULL == codec) {
277 SkDebugf("ERROR: no codec found for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000278 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000279 return false;
280 }
281
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000282 // In debug, the DLL will automatically be unloaded when this is deleted,
283 // but that shouldn't be a problem in release mode.
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000284 SkAutoTDelete<SkImageDecoder> ad(codec);
285
286 baseStream.rewind();
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000287 if (!codec->decode(&baseStream, diffRecord->fBaseBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000288 SkBitmap::kARGB_8888_Config,
289 SkImageDecoder::kDecodePixels_Mode)) {
290 SkDebugf("ERROR: codec failed for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000291 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000292 return false;
293 }
294
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000295 diffRecord->fBaseWidth = diffRecord->fBaseBitmap->width();
296 diffRecord->fBaseHeight = diffRecord->fBaseBitmap->height();
297
298 if (!codec->decode(&compareStream, diffRecord->fComparisonBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000299 SkBitmap::kARGB_8888_Config,
300 SkImageDecoder::kDecodePixels_Mode)) {
301 SkDebugf("ERROR: codec failed for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000302 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000303 return false;
304 }
305
306 return true;
307}
308
epoger@google.com5fd53852012-03-22 18:20:06 +0000309static bool get_bitmap_height_width(const SkString& path,
310 int *height, int *width) {
311 SkFILEStream stream(path.c_str());
312 if (!stream.isValid()) {
313 SkDebugf("ERROR: couldn't open file <%s>\n",
314 path.c_str());
315 return false;
316 }
317
318 SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
319 if (NULL == codec) {
320 SkDebugf("ERROR: no codec found for <%s>\n",
321 path.c_str());
322 return false;
323 }
324
325 SkAutoTDelete<SkImageDecoder> ad(codec);
326 SkBitmap bm;
327
328 stream.rewind();
329 if (!codec->decode(&stream, &bm,
330 SkBitmap::kARGB_8888_Config,
331 SkImageDecoder::kDecodePixels_Mode)) {
332 SkDebugf("ERROR: codec failed for <%s>\n",
333 path.c_str());
334 return false;
335 }
336
337 *height = bm.height();
338 *width = bm.width();
339
340 return true;
341}
342
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000343// from gm - thanks to PNG, we need to force all pixels 100% opaque
344static void force_all_opaque(const SkBitmap& bitmap) {
345 SkAutoLockPixels lock(bitmap);
346 for (int y = 0; y < bitmap.height(); y++) {
347 for (int x = 0; x < bitmap.width(); x++) {
348 *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
349 }
350 }
351}
352
353// from gm
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000354static bool write_bitmap(const SkString& path, const SkBitmap* bitmap) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000355 SkBitmap copy;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000356 bitmap->copyTo(&copy, SkBitmap::kARGB_8888_Config);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000357 force_all_opaque(copy);
358 return SkImageEncoder::EncodeFile(path.c_str(), copy,
359 SkImageEncoder::kPNG_Type, 100);
360}
361
362// from gm
363static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
364 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
365 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
366 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
367
368 return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
369}
370
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000371static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1,
372 const int threshold) {
373 int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
374 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
375 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
376 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
377
378 return ((SkAbs32(da) <= threshold) &&
379 (SkAbs32(dr) <= threshold) &&
380 (SkAbs32(dg) <= threshold) &&
381 (SkAbs32(db) <= threshold));
382}
383
384// based on gm
385static void compute_diff(DiffRecord* dr,
386 DiffMetricProc diffFunction,
387 const int colorThreshold) {
epoger@google.com25d961c2012-02-02 20:50:36 +0000388 SkAutoLockPixels alpDiff(*dr->fDifferenceBitmap);
389 SkAutoLockPixels alpWhite(*dr->fWhiteBitmap);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000390
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000391 const int w = dr->fComparisonBitmap->width();
392 const int h = dr->fComparisonBitmap->height();
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000393 int mismatchedPixels = 0;
394 int totalMismatchR = 0;
395 int totalMismatchG = 0;
396 int totalMismatchB = 0;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000397
398 if (w != dr->fBaseWidth || h != dr->fBaseHeight) {
399 dr->fDoImageSizesMismatch = true;
400 dr->fFractionDifference = 1;
401 return;
402 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000403 // Accumulate fractionally different pixels, then divide out
404 // # of pixels at the end.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000405 dr->fWeightedFraction = 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000406 for (int y = 0; y < h; y++) {
407 for (int x = 0; x < w; x++) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000408 SkPMColor c0 = *dr->fBaseBitmap->getAddr32(x, y);
409 SkPMColor c1 = *dr->fComparisonBitmap->getAddr32(x, y);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000410 SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
411 SkPMColor outputDifference = diffFunction(c0, c1);
412 uint32_t thisR = SkGetPackedR32(trueDifference);
413 uint32_t thisG = SkGetPackedG32(trueDifference);
414 uint32_t thisB = SkGetPackedB32(trueDifference);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000415 totalMismatchR += thisR;
416 totalMismatchG += thisG;
417 totalMismatchB += thisB;
418 // In HSV, value is defined as max RGB component.
419 int value = MAX3(thisR, thisG, thisB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000420 dr->fWeightedFraction += ((float) value) / 255;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000421 if (thisR > dr->fMaxMismatchR) {
422 dr->fMaxMismatchR = thisR;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000423 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000424 if (thisG > dr->fMaxMismatchG) {
425 dr->fMaxMismatchG = thisG;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000426 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000427 if (thisB > dr->fMaxMismatchB) {
428 dr->fMaxMismatchB = thisB;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000429 }
430 if (!colors_match_thresholded(c0, c1, colorThreshold)) {
431 mismatchedPixels++;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000432 *dr->fDifferenceBitmap->getAddr32(x, y) = outputDifference;
epoger@google.com25d961c2012-02-02 20:50:36 +0000433 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_WHITE;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000434 } else {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000435 *dr->fDifferenceBitmap->getAddr32(x, y) = 0;
epoger@google.com25d961c2012-02-02 20:50:36 +0000436 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_BLACK;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000437 }
438 }
439 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000440 int pixelCount = w * h;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000441 dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
442 dr->fWeightedFraction /= pixelCount;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000443 dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
444 dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
445 dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000446}
447
epoger@google.com25d961c2012-02-02 20:50:36 +0000448static SkString filename_to_derived_filename (const SkString& filename,
449 const char *suffix) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000450 SkString diffName (filename);
451 const char* cstring = diffName.c_str();
452 int dotOffset = strrchr(cstring, '.') - cstring;
453 diffName.remove(dotOffset, diffName.size() - dotOffset);
epoger@google.com25d961c2012-02-02 20:50:36 +0000454 diffName.append(suffix);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000455 return diffName;
456}
457
epoger@google.com25d961c2012-02-02 20:50:36 +0000458/// Given a image filename, returns the name of the file containing the
459/// associated difference image.
460static SkString filename_to_diff_filename (const SkString& filename) {
461 return filename_to_derived_filename(filename, "-diff.png");
462}
463
464/// Given a image filename, returns the name of the file containing the
465/// "white" difference image.
466static SkString filename_to_white_filename (const SkString& filename) {
467 return filename_to_derived_filename(filename, "-white.png");
468}
469
tomhudson@google.com7d042802011-07-14 13:15:55 +0000470/// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo-actual.png"
471static SkString chrome_expected_path_to_actual (const SkString& expected) {
472 SkString actualPath (expected);
473 actualPath.remove(actualPath.size() - 13, 13);
474 actualPath.append("-actual.png");
475 return actualPath;
476}
477
478/// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo.png"
479static SkString chrome_expected_name_to_short (const SkString& expected) {
480 SkString shortName (expected);
481 shortName.remove(shortName.size() - 13, 13);
482 shortName.append(".png");
483 return shortName;
484}
485
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000486static void release_bitmaps(DiffRecord* drp) {
487 delete drp->fBaseBitmap;
488 drp->fBaseBitmap = NULL;
489 delete drp->fComparisonBitmap;
490 drp->fComparisonBitmap = NULL;
491 delete drp->fDifferenceBitmap;
492 drp->fDifferenceBitmap = NULL;
epoger@google.com25d961c2012-02-02 20:50:36 +0000493 delete drp->fWhiteBitmap;
494 drp->fWhiteBitmap = NULL;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000495}
496
tomhudson@google.com7d042802011-07-14 13:15:55 +0000497
epoger@google.coma5f406e2012-05-01 13:26:16 +0000498/// If outputDir.isEmpty(), don't write out diff files.
tomhudson@google.com7d042802011-07-14 13:15:55 +0000499static void create_and_write_diff_image(DiffRecord* drp,
500 DiffMetricProc dmp,
501 const int colorThreshold,
502 const SkString& outputDir,
503 const SkString& filename) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000504 const int w = drp->fBaseWidth;
505 const int h = drp->fBaseHeight;
506 drp->fDifferenceBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
507 drp->fDifferenceBitmap->allocPixels();
epoger@google.com25d961c2012-02-02 20:50:36 +0000508 drp->fWhiteBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
509 drp->fWhiteBitmap->allocPixels();
tomhudson@google.com4e305982011-07-13 17:42:46 +0000510 compute_diff(drp, dmp, colorThreshold);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000511
epoger@google.coma5f406e2012-05-01 13:26:16 +0000512 if (!outputDir.isEmpty()) {
513 SkString differencePath (outputDir);
514 differencePath.append(filename_to_diff_filename(filename));
515 write_bitmap(differencePath, drp->fDifferenceBitmap);
516 SkString whitePath (outputDir);
517 whitePath.append(filename_to_white_filename(filename));
518 write_bitmap(whitePath, drp->fWhiteBitmap);
519 }
520
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000521 release_bitmaps(drp);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000522}
523
epoger@google.coma5f406e2012-05-01 13:26:16 +0000524/// Returns true if string contains any of these substrings.
525static bool string_contains_any_of(const SkString& string,
526 const StringArray& substrings) {
527 for (int i = 0; i < substrings.count(); i++) {
528 if (string.contains(substrings[i]->c_str())) {
529 return true;
530 }
531 }
532 return false;
533}
534
535/// Iterate over dir and get all files that:
536/// - match any of the substrings in matchSubstrings, but...
537/// - DO NOT match any of the substrings in nomatchSubstrings
538/// Returns the list of files in *files.
539static void get_file_list(const SkString& dir,
540 const StringArray& matchSubstrings,
541 const StringArray& nomatchSubstrings,
542 FileArray *files) {
epoger@google.com5fd53852012-03-22 18:20:06 +0000543 SkOSFile::Iter it(dir.c_str());
544 SkString filename;
545 while (it.next(&filename)) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000546 if (string_contains_any_of(filename, matchSubstrings) &&
547 !string_contains_any_of(filename, nomatchSubstrings)) {
548 files->push(new SkString(filename));
epoger@google.com5fd53852012-03-22 18:20:06 +0000549 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000550 }
551}
552
553static void release_file_list(FileArray *files) {
554 files->deleteAll();
555}
556
557/// Comparison routines for qsort, sort by file names.
558static int compare_file_name_metrics(SkString **lhs, SkString **rhs) {
559 return strcmp((*lhs)->c_str(), (*rhs)->c_str());
560}
561
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000562/// Creates difference images, returns the number that have a 0 metric.
epoger@google.coma5f406e2012-05-01 13:26:16 +0000563/// If outputDir.isEmpty(), don't write out diff files.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000564static void create_diff_images (DiffMetricProc dmp,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000565 const int colorThreshold,
566 RecordArray* differences,
567 const SkString& baseDir,
568 const SkString& comparisonDir,
569 const SkString& outputDir,
epoger@google.coma5f406e2012-05-01 13:26:16 +0000570 const StringArray& matchSubstrings,
571 const StringArray& nomatchSubstrings,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000572 DiffSummary* summary) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000573 SkASSERT(!baseDir.isEmpty());
574 SkASSERT(!comparisonDir.isEmpty());
epoger@google.com5fd53852012-03-22 18:20:06 +0000575 SkQSortCompareProc sortFileProc =
576 (SkQSortCompareProc)compare_file_name_metrics;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000577
epoger@google.com5fd53852012-03-22 18:20:06 +0000578 FileArray baseFiles;
579 FileArray comparisonFiles;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000580
epoger@google.coma5f406e2012-05-01 13:26:16 +0000581 get_file_list(baseDir, matchSubstrings, nomatchSubstrings, &baseFiles);
582 get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings,
583 &comparisonFiles);
epoger@google.com5fd53852012-03-22 18:20:06 +0000584
epoger@google.coma5f406e2012-05-01 13:26:16 +0000585 if (!baseFiles.isEmpty()) {
586 SkQSort(baseFiles.begin(), baseFiles.count(),
587 sizeof(SkString*), sortFileProc);
588 }
589 if (!comparisonFiles.isEmpty()) {
590 SkQSort(comparisonFiles.begin(), comparisonFiles.count(),
591 sizeof(SkString*), sortFileProc);
592 }
593
epoger@google.com5fd53852012-03-22 18:20:06 +0000594 int i = 0;
595 int j = 0;
596
597 while (i < baseFiles.count() &&
598 j < comparisonFiles.count()) {
599
tomhudson@google.com4e305982011-07-13 17:42:46 +0000600 SkString basePath (baseDir);
epoger@google.com5fd53852012-03-22 18:20:06 +0000601 basePath.append(*baseFiles[i]);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000602 SkString comparisonPath (comparisonDir);
epoger@google.com5fd53852012-03-22 18:20:06 +0000603 comparisonPath.append(*comparisonFiles[j]);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000604
epoger@google.com5fd53852012-03-22 18:20:06 +0000605 DiffRecord *drp = NULL;
606 int v = strcmp(baseFiles[i]->c_str(),
607 comparisonFiles[j]->c_str());
608
609 if (v < 0) {
610 // in baseDir, but not in comparisonDir
611 drp = new DiffRecord(*baseFiles[i],
612 basePath, comparisonPath, false, true);
613 ++i;
614 } else if (v > 0) {
615 // in comparisonDir, but not in baseDir
616 drp = new DiffRecord(*comparisonFiles[j],
617 basePath, comparisonPath, true, false);
618 ++j;
619 } else {
620 // let's diff!
621 drp = new DiffRecord(*baseFiles[i], basePath, comparisonPath);
622
623 if (get_bitmaps(drp)) {
624 create_and_write_diff_image(drp, dmp, colorThreshold,
625 outputDir, *baseFiles[i]);
626 }
627
628 ++i;
629 ++j;
630 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000631
632 differences->push(drp);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000633 summary->add(drp);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000634 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000635
636 for (; i < baseFiles.count(); ++i) {
637 // files only in baseDir
638 SkString basePath (baseDir);
639 basePath.append(*baseFiles[i]);
640 SkString comparisonPath;
641 DiffRecord *drp = new DiffRecord(*baseFiles[i], basePath,
642 comparisonPath, false, true);
643 differences->push(drp);
644 summary->add(drp);
645 }
646
647 for (; j < comparisonFiles.count(); ++j) {
648 // files only in comparisonDir
649 SkString basePath;
650 SkString comparisonPath(comparisonDir);
651 comparisonPath.append(*comparisonFiles[j]);
652 DiffRecord *drp = new DiffRecord(*comparisonFiles[j], basePath,
653 comparisonPath, true, false);
654 differences->push(drp);
655 summary->add(drp);
656 }
657
658 release_file_list(&baseFiles);
659 release_file_list(&comparisonFiles);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000660}
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000661
tomhudson@google.com7d042802011-07-14 13:15:55 +0000662static void create_diff_images_chromium (DiffMetricProc dmp,
663 const int colorThreshold,
664 RecordArray* differences,
665 const SkString& dirname,
666 const SkString& outputDir,
667 DiffSummary* summary) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000668 SkASSERT(!outputDir.isEmpty());
669
tomhudson@google.com7d042802011-07-14 13:15:55 +0000670 SkOSFile::Iter baseIterator (dirname.c_str());
671 SkString filename;
672 while (baseIterator.next(&filename)) {
673 if (filename.endsWith(".pdf")) {
674 continue;
675 }
676 if (filename.endsWith("-expected.png")) {
677 SkString expectedPath (dirname);
678 expectedPath.append(filename);
679 SkString shortName (chrome_expected_name_to_short(filename));
680 SkString actualPath (chrome_expected_path_to_actual(expectedPath));
681 DiffRecord * drp =
682 new DiffRecord (shortName, expectedPath, actualPath);
683 if (!get_bitmaps(drp)) {
684 continue;
685 }
686 create_and_write_diff_image(drp, dmp, colorThreshold,
687 outputDir, shortName);
688
689 differences->push(drp);
690 summary->add(drp);
691 }
692 }
693}
694
695static void analyze_chromium(DiffMetricProc dmp,
696 const int colorThreshold,
697 RecordArray* differences,
698 const SkString& dirname,
699 const SkString& outputDir,
700 DiffSummary* summary) {
epoger@google.coma5f406e2012-05-01 13:26:16 +0000701 SkASSERT(!outputDir.isEmpty());
tomhudson@google.com7d042802011-07-14 13:15:55 +0000702 create_diff_images_chromium(dmp, colorThreshold, differences,
703 dirname, outputDir, summary);
704 SkOSFile::Iter dirIterator(dirname.c_str());
705 SkString newdirname;
706 while (dirIterator.next(&newdirname, true)) {
707 if (newdirname.startsWith(".")) {
708 continue;
709 }
710 SkString fullname (dirname);
711 fullname.append(newdirname);
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000712 if (!fullname.endsWith(PATH_DIV_STR)) {
713 fullname.append(PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000714 }
715 analyze_chromium(dmp, colorThreshold, differences,
716 fullname, outputDir, summary);
717 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000718}
719
720/// Make layout more consistent by scaling image to 240 height, 360 width,
721/// or natural size, whichever is smallest.
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000722static int compute_image_height (int height, int width) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000723 int retval = 240;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000724 if (height < retval) {
725 retval = height;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000726 }
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000727 float scale = (float) retval / height;
728 if (width * scale > 360) {
729 scale = (float) 360 / width;
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000730 retval = static_cast<int>(height * scale);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000731 }
732 return retval;
733}
734
epoger@google.com25d961c2012-02-02 20:50:36 +0000735static void print_table_header (SkFILEWStream* stream,
736 const int matchCount,
737 const int colorThreshold,
738 const RecordArray& differences,
739 const SkString &baseDir,
740 const SkString &comparisonDir) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000741 SkTime::DateTime dt;
742 SkTime::GetDateTime(&dt);
epoger@google.com25d961c2012-02-02 20:50:36 +0000743 stream->writeText("<table>\n");
744 stream->writeText("<tr><th>");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000745 stream->writeText("SkDiff run at ");
746 stream->writeDecAsText(dt.fHour);
747 stream->writeText(":");
748 if (dt.fMinute < 10) {
749 stream->writeText("0");
750 }
751 stream->writeDecAsText(dt.fMinute);
752 stream->writeText(":");
753 if (dt.fSecond < 10) {
754 stream->writeText("0");
755 }
756 stream->writeDecAsText(dt.fSecond);
757 stream->writeText("<br>");
758 stream->writeDecAsText(matchCount);
759 stream->writeText(" of ");
760 stream->writeDecAsText(differences.count());
761 stream->writeText(" images matched ");
762 if (colorThreshold == 0) {
763 stream->writeText("exactly");
764 } else {
765 stream->writeText("within ");
766 stream->writeDecAsText(colorThreshold);
767 stream->writeText(" color units per component");
768 }
769 stream->writeText(".<br>");
epoger@google.com25d961c2012-02-02 20:50:36 +0000770 stream->writeText("</th>\n<th>");
771 stream->writeText("every different pixel shown in white");
772 stream->writeText("</th>\n<th>");
773 stream->writeText("color difference at each pixel");
774 stream->writeText("</th>\n<th>");
775 stream->writeText(baseDir.c_str());
776 stream->writeText("</th>\n<th>");
777 stream->writeText(comparisonDir.c_str());
778 stream->writeText("</th>\n");
779 stream->writeText("</tr>\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000780}
781
782static void print_pixel_count (SkFILEWStream* stream,
783 const DiffRecord& diff) {
784 stream->writeText("<br>(");
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000785 stream->writeDecAsText(static_cast<int>(diff.fFractionDifference *
786 diff.fBaseWidth *
787 diff.fBaseHeight));
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000788 stream->writeText(" pixels)");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000789/*
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000790 stream->writeDecAsText(diff.fWeightedFraction *
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000791 diff.fBaseWidth *
792 diff.fBaseHeight);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000793 stream->writeText(" weighted pixels)");
794*/
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000795}
796
797static void print_label_cell (SkFILEWStream* stream,
798 const DiffRecord& diff) {
799 stream->writeText("<td>");
800 stream->writeText(diff.fFilename.c_str());
801 stream->writeText("<br>");
epoger@google.com5fd53852012-03-22 18:20:06 +0000802 if (diff.fBaseMissing || diff.fComparisonMissing) {
803 stream->writeText("</td>");
804 return;
805 }
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000806 if (diff.fDoImageSizesMismatch) {
807 stream->writeText("Image sizes differ");
808 stream->writeText("</td>");
809 return;
810 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000811 char metricBuf [20];
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000812 sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000813 stream->writeText(metricBuf);
814 stream->writeText(" of pixels differ");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000815 stream->writeText("\n (");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000816 sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000817 stream->writeText(metricBuf);
818 stream->writeText(" weighted)");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000819 // Write the actual number of pixels that differ if it's < 1%
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000820 if (diff.fFractionDifference < 0.01) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000821 print_pixel_count(stream, diff);
822 }
823 stream->writeText("<br>Average color mismatch ");
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000824 stream->writeDecAsText(static_cast<int>(MAX3(diff.fAverageMismatchR,
825 diff.fAverageMismatchG,
826 diff.fAverageMismatchB)));
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000827 stream->writeText("<br>Max color mismatch ");
828 stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
829 diff.fMaxMismatchG,
830 diff.fMaxMismatchB));
831 stream->writeText("</td>");
832}
833
834static void print_image_cell (SkFILEWStream* stream,
tomhudson@google.com4e305982011-07-13 17:42:46 +0000835 const SkString& path,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000836 int height) {
837 stream->writeText("<td><a href=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000838 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000839 stream->writeText("\"><img src=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000840 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000841 stream->writeText("\" height=\"");
842 stream->writeDecAsText(height);
843 stream->writeText("px\"></a></td>");
844}
845
epoger@google.com01f78702012-04-12 16:32:04 +0000846static void print_text_cell (SkFILEWStream* stream, const char* text) {
847 stream->writeText("<td align=center>");
848 if (NULL != text) {
849 stream->writeText(text);
850 }
851 stream->writeText("</td>");
852}
853
epoger@google.com5fd53852012-03-22 18:20:06 +0000854static void print_diff_with_missing_file(SkFILEWStream* stream,
855 DiffRecord& diff,
856 const SkString& relativePath) {
857 stream->writeText("<tr>\n");
858 print_label_cell(stream, diff);
859 stream->writeText("<td>N/A</td>");
860 stream->writeText("<td>N/A</td>");
861 if (!diff.fBaseMissing) {
862 int h, w;
863 if (!get_bitmap_height_width(diff.fBasePath, &h, &w)) {
864 stream->writeText("<td>N/A</td>");
865 } else {
866 int height = compute_image_height(h, w);
867 if (!diff.fBasePath.startsWith(PATH_DIV_STR)) {
868 diff.fBasePath.prepend(relativePath);
869 }
870 print_image_cell(stream, diff.fBasePath, height);
871 }
872 } else {
873 stream->writeText("<td>N/A</td>");
874 }
875 if (!diff.fComparisonMissing) {
876 int h, w;
877 if (!get_bitmap_height_width(diff.fComparisonPath, &h, &w)) {
878 stream->writeText("<td>N/A</td>");
879 } else {
880 int height = compute_image_height(h, w);
881 if (!diff.fComparisonPath.startsWith(PATH_DIV_STR)) {
882 diff.fComparisonPath.prepend(relativePath);
883 }
884 print_image_cell(stream, diff.fComparisonPath, height);
885 }
886 } else {
887 stream->writeText("<td>N/A</td>");
888 }
889 stream->writeText("</tr>\n");
890 stream->flush();
891}
892
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000893static void print_diff_page (const int matchCount,
894 const int colorThreshold,
895 const RecordArray& differences,
896 const SkString& baseDir,
897 const SkString& comparisonDir,
898 const SkString& outputDir) {
899
epoger@google.coma5f406e2012-05-01 13:26:16 +0000900 SkASSERT(!baseDir.isEmpty());
901 SkASSERT(!comparisonDir.isEmpty());
902 SkASSERT(!outputDir.isEmpty());
903
tomhudson@google.com5b325292011-05-24 19:41:13 +0000904 SkString outputPath (outputDir);
905 outputPath.append("index.html");
906 //SkFILEWStream outputStream ("index.html");
907 SkFILEWStream outputStream (outputPath.c_str());
908
tomhudson@google.com4e305982011-07-13 17:42:46 +0000909 // Need to convert paths from relative-to-cwd to relative-to-outputDir
tomhudson@google.com5b325292011-05-24 19:41:13 +0000910 // FIXME this doesn't work if there are '..' inside the outputDir
911 unsigned int ui;
912 SkString relativePath;
913 for (ui = 0; ui < outputDir.size(); ui++) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000914 if (outputDir[ui] == PATH_DIV_CHAR) {
915 relativePath.append(".." PATH_DIV_STR);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000916 }
917 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000918
919 outputStream.writeText("<html>\n<body>\n");
epoger@google.com25d961c2012-02-02 20:50:36 +0000920 print_table_header(&outputStream, matchCount, colorThreshold, differences,
921 baseDir, comparisonDir);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000922 int i;
923 for (i = 0; i < differences.count(); i++) {
924 DiffRecord* diff = differences[i];
epoger@google.com5fd53852012-03-22 18:20:06 +0000925
926 if (diff->fBaseMissing || diff->fComparisonMissing) {
927 print_diff_with_missing_file(&outputStream, *diff, relativePath);
928 continue;
929 } else if (0 == diff->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000930 continue;
931 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000932
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000933 if (!diff->fBasePath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000934 diff->fBasePath.prepend(relativePath);
935 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000936 if (!diff->fComparisonPath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000937 diff->fComparisonPath.prepend(relativePath);
938 }
epoger@google.com5fd53852012-03-22 18:20:06 +0000939
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000940 int height = compute_image_height(diff->fBaseHeight, diff->fBaseWidth);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000941 outputStream.writeText("<tr>\n");
942 print_label_cell(&outputStream, *diff);
epoger@google.com01f78702012-04-12 16:32:04 +0000943 if (diff->fDoImageSizesMismatch) {
944 print_text_cell(&outputStream,
945 "[image size mismatch, so no diff to display]");
946 print_text_cell(&outputStream,
947 "[image size mismatch, so no diff to display]");
948 } else {
949 print_image_cell(&outputStream,
950 filename_to_white_filename(diff->fFilename), height);
951 print_image_cell(&outputStream,
952 filename_to_diff_filename(diff->fFilename), height);
953 }
epoger@google.com25d961c2012-02-02 20:50:36 +0000954 print_image_cell(&outputStream, diff->fBasePath, height);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000955 print_image_cell(&outputStream, diff->fComparisonPath, height);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000956 outputStream.writeText("</tr>\n");
957 outputStream.flush();
958 }
959 outputStream.writeText("</table>\n");
960 outputStream.writeText("</body>\n</html>\n");
961 outputStream.flush();
962}
963
964static void usage (char * argv0) {
965 SkDebugf("Skia baseline image diff tool\n");
epoger@google.coma5f406e2012-05-01 13:26:16 +0000966 SkDebugf("\n"
967"Usage: \n"
968" %s <baseDir> <comparisonDir> [outputDir] \n"
969"or \n"
970" %s --chromium-release|--chromium-debug <baseDir> <outputDir> \n",
971argv0, argv0);
972 SkDebugf("\n"
973"Arguments: \n"
974" --nodiffs: don't write out image diffs or index.html, just generate \n"
975" report on stdout \n"
976" --threshold <n>: only report differences > n (per color channel) [default 0]\n"
977" --match: compare files whose filenames contain this substring; if \n"
978" unspecified, compare ALL files. \n"
979" this flag may be repeated to add more matching substrings. \n"
980" --nomatch: regardless of --match, DO NOT compare files whose filenames \n"
981" contain this substring. \n"
982" this flag may be repeated to add more forbidden substrings. \n"
tomhudson@google.com7d042802011-07-14 13:15:55 +0000983" --sortbymismatch: sort by average color channel mismatch\n");
984 SkDebugf(
epoger@google.coma5f406e2012-05-01 13:26:16 +0000985" --sortbymaxmismatch: sort by worst color channel mismatch;\n"
986" break ties with -sortbymismatch\n"
987" [default sort is by fraction of pixels mismatching]\n");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000988 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000989" --weighted: sort by # pixels different weighted by color difference\n");
990 SkDebugf(
991" --chromium-release: process Webkit LayoutTests results instead of gm\n"
992" --chromium-debug: process Webkit LayoutTests results instead of gm\n");
993 SkDebugf(
994" baseDir: directory to read baseline images from,\n"
995" or chromium/src directory for --chromium.\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000996 SkDebugf(
epoger@google.coma5f406e2012-05-01 13:26:16 +0000997" comparisonDir: directory to read comparison images from\n");
998 SkDebugf(
999" outputDir: directory to write difference images and index.html to; \n"
1000" defaults to comparisonDir when not running --chromium \n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001001}
1002
1003int main (int argc, char ** argv) {
1004 DiffMetricProc diffProc = compute_diff_pmcolor;
1005 SkQSortCompareProc sortProc = (SkQSortCompareProc) compare_diff_metrics;
1006
1007 // Maximum error tolerated in any one color channel in any one pixel before
1008 // a difference is reported.
1009 int colorThreshold = 0;
1010 SkString baseDir;
1011 SkString comparisonDir;
1012 SkString outputDir;
epoger@google.coma5f406e2012-05-01 13:26:16 +00001013 StringArray matchSubstrings;
1014 StringArray nomatchSubstrings;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001015
tomhudson@google.com7d042802011-07-14 13:15:55 +00001016 bool analyzeChromium = false;
1017 bool chromiumDebug = false;
1018 bool chromiumRelease = false;
epoger@google.coma5f406e2012-05-01 13:26:16 +00001019 bool generateDiffs = true;
tomhudson@google.com7d042802011-07-14 13:15:55 +00001020
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001021 RecordArray differences;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001022 DiffSummary summary;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001023
epoger@google.coma5f406e2012-05-01 13:26:16 +00001024 int i;
1025 int numUnflaggedArguments = 0;
1026 for (i = 1; i < argc; i++) {
tomhudson@google.com7d042802011-07-14 13:15:55 +00001027 if (!strcmp(argv[i], "--help")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001028 usage(argv[0]);
1029 return 0;
1030 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001031 if (!strcmp(argv[i], "--nodiffs")) {
1032 generateDiffs = false;
1033 continue;
1034 }
1035 if (!strcmp(argv[i], "--threshold")) {
1036 colorThreshold = atoi(argv[++i]);
1037 continue;
1038 }
1039 if (!strcmp(argv[i], "--match")) {
1040 matchSubstrings.push(new SkString(argv[++i]));
1041 continue;
1042 }
1043 if (!strcmp(argv[i], "--nomatch")) {
1044 nomatchSubstrings.push(new SkString(argv[++i]));
1045 continue;
1046 }
tomhudson@google.com7d042802011-07-14 13:15:55 +00001047 if (!strcmp(argv[i], "--sortbymismatch")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001048 sortProc = (SkQSortCompareProc) compare_diff_mean_mismatches;
1049 continue;
1050 }
tomhudson@google.com7d042802011-07-14 13:15:55 +00001051 if (!strcmp(argv[i], "--sortbymaxmismatch")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001052 sortProc = (SkQSortCompareProc) compare_diff_max_mismatches;
1053 continue;
1054 }
tomhudson@google.com7d042802011-07-14 13:15:55 +00001055 if (!strcmp(argv[i], "--weighted")) {
tomhudson@google.com5b325292011-05-24 19:41:13 +00001056 sortProc = (SkQSortCompareProc) compare_diff_weighted;
1057 continue;
1058 }
tomhudson@google.com7d042802011-07-14 13:15:55 +00001059 if (!strcmp(argv[i], "--chromium-release")) {
1060 analyzeChromium = true;
1061 chromiumRelease = true;
1062 continue;
1063 }
1064 if (!strcmp(argv[i], "--chromium-debug")) {
1065 analyzeChromium = true;
1066 chromiumDebug = true;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001067 continue;
1068 }
1069 if (argv[i][0] != '-') {
epoger@google.coma5f406e2012-05-01 13:26:16 +00001070 switch (numUnflaggedArguments++) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001071 case 0:
1072 baseDir.set(argv[i]);
1073 continue;
1074 case 1:
1075 comparisonDir.set(argv[i]);
1076 continue;
1077 case 2:
1078 outputDir.set(argv[i]);
1079 continue;
1080 default:
epoger@google.coma5f406e2012-05-01 13:26:16 +00001081 SkDebugf("extra unflagged argument <%s>\n", argv[i]);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001082 usage(argv[0]);
1083 return 0;
1084 }
1085 }
1086
1087 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
1088 usage(argv[0]);
1089 return 0;
1090 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001091
tomhudson@google.com7d042802011-07-14 13:15:55 +00001092 if (analyzeChromium) {
epoger@google.coma5f406e2012-05-01 13:26:16 +00001093 if (numUnflaggedArguments != 2) {
tomhudson@google.com7d042802011-07-14 13:15:55 +00001094 usage(argv[0]);
1095 return 0;
1096 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001097 outputDir = comparisonDir;
tomhudson@google.com7d042802011-07-14 13:15:55 +00001098 if (chromiumRelease && chromiumDebug) {
1099 SkDebugf(
1100"--chromium must be either -release or -debug, not both!\n");
1101 return 0;
1102 }
1103 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001104
1105 if (numUnflaggedArguments == 2) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001106 outputDir = comparisonDir;
epoger@google.coma5f406e2012-05-01 13:26:16 +00001107 } else if (numUnflaggedArguments != 3) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001108 usage(argv[0]);
1109 return 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001110 }
1111
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001112 if (!baseDir.endsWith(PATH_DIV_STR)) {
1113 baseDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001114 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001115 printf("baseDir is [%s]\n", baseDir.c_str());
1116
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001117 if (!comparisonDir.endsWith(PATH_DIV_STR)) {
1118 comparisonDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001119 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001120 printf("comparisonDir is [%s]\n", comparisonDir.c_str());
1121
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001122 if (!outputDir.endsWith(PATH_DIV_STR)) {
1123 outputDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001124 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001125 if (generateDiffs) {
1126 printf("writing diffs to outputDir is [%s]\n", outputDir.c_str());
1127 } else {
1128 printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str());
1129 outputDir.set("");
1130 }
1131
1132 // Default substring matching:
1133 // - No matter what, don't match any PDF files.
1134 // We may want to change this later, but for now this maintains the filter
1135 // that get_file_list() used to always apply.
1136 // - If no matchSubstrings were specified, match ALL strings.
1137 nomatchSubstrings.push(new SkString(".pdf"));
1138 if (matchSubstrings.isEmpty()) {
1139 matchSubstrings.push(new SkString(""));
1140 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001141
tomhudson@google.com7d042802011-07-14 13:15:55 +00001142 if (analyzeChromium) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001143 baseDir.append("webkit" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001144 if (chromiumRelease) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001145 baseDir.append("Release" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001146 }
1147 if (chromiumDebug) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001148 baseDir.append("Debug" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001149 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +00001150 baseDir.append("layout-test-results" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001151 analyze_chromium(diffProc, colorThreshold, &differences,
1152 baseDir, outputDir, &summary);
1153 } else {
1154 create_diff_images(diffProc, colorThreshold, &differences,
epoger@google.coma5f406e2012-05-01 13:26:16 +00001155 baseDir, comparisonDir, outputDir,
1156 matchSubstrings, nomatchSubstrings, &summary);
tomhudson@google.com7d042802011-07-14 13:15:55 +00001157 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +00001158 summary.print();
tomhudson@google.com7d042802011-07-14 13:15:55 +00001159
1160 if (differences.count()) {
1161 SkQSort(differences.begin(), differences.count(),
1162 sizeof(DiffRecord*), sortProc);
1163 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001164
1165 if (generateDiffs) {
1166 print_diff_page(summary.fNumMatches, colorThreshold, differences,
1167 baseDir, comparisonDir, outputDir);
1168 }
1169
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001170 for (i = 0; i < differences.count(); i++) {
1171 delete differences[i];
1172 }
epoger@google.coma5f406e2012-05-01 13:26:16 +00001173 matchSubstrings.deleteAll();
1174 nomatchSubstrings.deleteAll();
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001175}