blob: 3d2925b9c7486313a02a4f4de8abf2b73465fefa [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,
46 const SkString comparisonPath)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000047 : fFilename (filename)
tomhudson@google.com4e305982011-07-13 17:42:46 +000048 , fBasePath (basePath)
49 , fComparisonPath (comparisonPath)
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000050 , fBaseBitmap (new SkBitmap ())
51 , fComparisonBitmap (new SkBitmap ())
52 , fDifferenceBitmap (new SkBitmap ())
53 , fBaseHeight (0)
54 , fBaseWidth (0)
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000055 , fFractionDifference (0)
56 , fWeightedFraction (0)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000057 , fAverageMismatchR (0)
58 , fAverageMismatchG (0)
59 , fAverageMismatchB (0)
60 , fMaxMismatchR (0)
61 , fMaxMismatchG (0)
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +000062 , fMaxMismatchB (0)
63 , fDoImageSizesMismatch (false) {
tomhudson@google.com7d042802011-07-14 13:15:55 +000064 // These asserts are valid for GM, but not for --chromium
65 //SkASSERT(basePath.endsWith(filename.c_str()));
66 //SkASSERT(comparisonPath.endsWith(filename.c_str()));
tomhudson@google.com4e305982011-07-13 17:42:46 +000067 };
tomhudson@google.com4b33d282011-04-27 15:39:30 +000068
69 SkString fFilename;
tomhudson@google.com4e305982011-07-13 17:42:46 +000070 SkString fBasePath;
71 SkString fComparisonPath;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000072
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000073 SkBitmap* fBaseBitmap;
74 SkBitmap* fComparisonBitmap;
75 SkBitmap* fDifferenceBitmap;
76
77 int fBaseHeight;
78 int fBaseWidth;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000079
80 /// Arbitrary floating-point metric to be used to sort images from most
81 /// to least different from baseline; values of 0 will be omitted from the
82 /// summary webpage.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000083 float fFractionDifference;
84 float fWeightedFraction;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000085
86 float fAverageMismatchR;
87 float fAverageMismatchG;
88 float fAverageMismatchB;
89
90 uint32_t fMaxMismatchR;
91 uint32_t fMaxMismatchG;
92 uint32_t fMaxMismatchB;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +000093
94 /// By the time we need to report image size mismatch, we've already
95 /// released the bitmaps, so we need to remember it when we detect it.
96 bool fDoImageSizesMismatch;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000097};
98
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000099#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
100#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
101
102struct DiffSummary {
103 DiffSummary ()
104 : fNumMatches (0)
105 , fNumMismatches (0)
106 , fMaxMismatchV (0)
107 , fMaxMismatchPercent (0) { };
108
109 uint32_t fNumMatches;
110 uint32_t fNumMismatches;
111 uint32_t fMaxMismatchV;
112 float fMaxMismatchPercent;
113
114 void print () {
115 printf("%d of %d images matched.\n", fNumMatches,
116 fNumMatches + fNumMismatches);
117 if (fNumMismatches > 0) {
118 printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
119 printf("Largest area mismatch was %.2f%% of pixels\n",
120 fMaxMismatchPercent);
121 }
122
123 }
124
125 void add (DiffRecord* drp) {
126 if (0 == drp->fFractionDifference) {
127 fNumMatches++;
128 } else {
129 fNumMismatches++;
130 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
131 fMaxMismatchPercent = drp->fFractionDifference * 100;
132 }
tomhudson@google.com88a0e052011-06-09 18:54:01 +0000133 uint32_t value = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
134 drp->fMaxMismatchB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000135 if (value > fMaxMismatchV) {
136 fMaxMismatchV = value;
137 }
138 }
139 }
140};
141
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000142typedef SkTDArray<DiffRecord*> RecordArray;
143
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000144/// Comparison routine for qsort; sorts by fFractionDifference
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000145/// from largest to smallest.
146static int compare_diff_metrics (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000147 if ((*lhs)->fFractionDifference < (*rhs)->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000148 return 1;
149 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000150 if ((*rhs)->fFractionDifference < (*lhs)->fFractionDifference) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000151 return -1;
152 }
153 return 0;
154}
155
156static int compare_diff_weighted (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000157 if ((*lhs)->fWeightedFraction < (*rhs)->fWeightedFraction) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000158 return 1;
159 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000160 if ((*lhs)->fWeightedFraction > (*rhs)->fWeightedFraction) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000161 return -1;
162 }
163 return 0;
164}
165
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000166/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
167/// from largest to smallest.
168static int compare_diff_mean_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
169 float leftValue = MAX3((*lhs)->fAverageMismatchR,
170 (*lhs)->fAverageMismatchG,
171 (*lhs)->fAverageMismatchB);
172 float rightValue = MAX3((*rhs)->fAverageMismatchR,
173 (*rhs)->fAverageMismatchG,
174 (*rhs)->fAverageMismatchB);
175 if (leftValue < rightValue) {
176 return 1;
177 }
178 if (rightValue < leftValue) {
179 return -1;
180 }
181 return 0;
182}
183
184/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB})
185/// from largest to smallest.
186static int compare_diff_max_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000187 uint32_t leftValue = MAX3((*lhs)->fMaxMismatchR,
188 (*lhs)->fMaxMismatchG,
189 (*lhs)->fMaxMismatchB);
190 uint32_t rightValue = MAX3((*rhs)->fMaxMismatchR,
191 (*rhs)->fMaxMismatchG,
192 (*rhs)->fMaxMismatchB);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000193 if (leftValue < rightValue) {
194 return 1;
195 }
196 if (rightValue < leftValue) {
197 return -1;
198 }
199 return compare_diff_mean_mismatches(lhs, rhs);
200}
201
202
203
204/// Parameterized routine to compute the color of a pixel in a difference image.
205typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
206
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000207static void expand_and_copy (int width, int height, SkBitmap** dest) {
208 SkBitmap* temp = new SkBitmap ();
209 temp->reset();
210 temp->setConfig((*dest)->config(), width, height);
211 temp->allocPixels();
212 (*dest)->copyPixelsTo(temp->getPixels(), temp->getSize(),
213 temp->rowBytes());
214 *dest = temp;
215}
216
tomhudson@google.com4e305982011-07-13 17:42:46 +0000217static bool get_bitmaps (DiffRecord* diffRecord) {
218 SkFILEStream compareStream(diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000219 if (!compareStream.isValid()) {
220 SkDebugf("WARNING: couldn't open comparison file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000221 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000222 return false;
223 }
224
tomhudson@google.com4e305982011-07-13 17:42:46 +0000225 SkFILEStream baseStream(diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000226 if (!baseStream.isValid()) {
227 SkDebugf("ERROR: couldn't open base file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000228 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000229 return false;
230 }
231
232 SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
233 if (NULL == codec) {
234 SkDebugf("ERROR: no codec found for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000235 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000236 return false;
237 }
238
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000239 // In debug, the DLL will automatically be unloaded when this is deleted,
240 // but that shouldn't be a problem in release mode.
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000241 SkAutoTDelete<SkImageDecoder> ad(codec);
242
243 baseStream.rewind();
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000244 if (!codec->decode(&baseStream, diffRecord->fBaseBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000245 SkBitmap::kARGB_8888_Config,
246 SkImageDecoder::kDecodePixels_Mode)) {
247 SkDebugf("ERROR: codec failed for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000248 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000249 return false;
250 }
251
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000252 diffRecord->fBaseWidth = diffRecord->fBaseBitmap->width();
253 diffRecord->fBaseHeight = diffRecord->fBaseBitmap->height();
254
255 if (!codec->decode(&compareStream, diffRecord->fComparisonBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000256 SkBitmap::kARGB_8888_Config,
257 SkImageDecoder::kDecodePixels_Mode)) {
258 SkDebugf("ERROR: codec failed for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000259 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000260 return false;
261 }
262
263 return true;
264}
265
266// from gm - thanks to PNG, we need to force all pixels 100% opaque
267static void force_all_opaque(const SkBitmap& bitmap) {
268 SkAutoLockPixels lock(bitmap);
269 for (int y = 0; y < bitmap.height(); y++) {
270 for (int x = 0; x < bitmap.width(); x++) {
271 *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
272 }
273 }
274}
275
276// from gm
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000277static bool write_bitmap(const SkString& path, const SkBitmap* bitmap) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000278 SkBitmap copy;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000279 bitmap->copyTo(&copy, SkBitmap::kARGB_8888_Config);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000280 force_all_opaque(copy);
281 return SkImageEncoder::EncodeFile(path.c_str(), copy,
282 SkImageEncoder::kPNG_Type, 100);
283}
284
285// from gm
286static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
287 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
288 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
289 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
290
291 return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
292}
293
294/// Returns white on every pixel so that differences jump out at you;
295/// makes it easy to spot areas of difference that are in the least-significant
296/// bits.
297static inline SkPMColor compute_diff_white(SkPMColor c0, SkPMColor c1) {
298 return SkPackARGB32(0xFF, 0xFF, 0xFF, 0xFF);
299}
300
301static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1,
302 const int threshold) {
303 int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
304 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
305 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
306 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
307
308 return ((SkAbs32(da) <= threshold) &&
309 (SkAbs32(dr) <= threshold) &&
310 (SkAbs32(dg) <= threshold) &&
311 (SkAbs32(db) <= threshold));
312}
313
314// based on gm
315static void compute_diff(DiffRecord* dr,
316 DiffMetricProc diffFunction,
317 const int colorThreshold) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000318 SkAutoLockPixels alp(*dr->fDifferenceBitmap);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000319
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000320 const int w = dr->fComparisonBitmap->width();
321 const int h = dr->fComparisonBitmap->height();
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000322 int mismatchedPixels = 0;
323 int totalMismatchR = 0;
324 int totalMismatchG = 0;
325 int totalMismatchB = 0;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000326
327 if (w != dr->fBaseWidth || h != dr->fBaseHeight) {
328 dr->fDoImageSizesMismatch = true;
329 dr->fFractionDifference = 1;
330 return;
331 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000332 // Accumulate fractionally different pixels, then divide out
333 // # of pixels at the end.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000334 dr->fWeightedFraction = 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000335 for (int y = 0; y < h; y++) {
336 for (int x = 0; x < w; x++) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000337 SkPMColor c0 = *dr->fBaseBitmap->getAddr32(x, y);
338 SkPMColor c1 = *dr->fComparisonBitmap->getAddr32(x, y);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000339 SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
340 SkPMColor outputDifference = diffFunction(c0, c1);
341 uint32_t thisR = SkGetPackedR32(trueDifference);
342 uint32_t thisG = SkGetPackedG32(trueDifference);
343 uint32_t thisB = SkGetPackedB32(trueDifference);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000344 totalMismatchR += thisR;
345 totalMismatchG += thisG;
346 totalMismatchB += thisB;
347 // In HSV, value is defined as max RGB component.
348 int value = MAX3(thisR, thisG, thisB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000349 dr->fWeightedFraction += ((float) value) / 255;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000350 if (thisR > dr->fMaxMismatchR) {
351 dr->fMaxMismatchR = thisR;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000352 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000353 if (thisG > dr->fMaxMismatchG) {
354 dr->fMaxMismatchG = thisG;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000355 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000356 if (thisB > dr->fMaxMismatchB) {
357 dr->fMaxMismatchB = thisB;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000358 }
359 if (!colors_match_thresholded(c0, c1, colorThreshold)) {
360 mismatchedPixels++;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000361 *dr->fDifferenceBitmap->getAddr32(x, y) = outputDifference;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000362 } else {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000363 *dr->fDifferenceBitmap->getAddr32(x, y) = 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000364 }
365 }
366 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000367 int pixelCount = w * h;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000368 dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
369 dr->fWeightedFraction /= pixelCount;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000370 dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
371 dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
372 dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000373}
374
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000375/// Given a image filename, returns the name of the file containing the
376/// associated difference image.
377static SkString filename_to_diff_filename (const SkString& filename) {
378 SkString diffName (filename);
379 const char* cstring = diffName.c_str();
380 int dotOffset = strrchr(cstring, '.') - cstring;
381 diffName.remove(dotOffset, diffName.size() - dotOffset);
382 diffName.append("-diff.png");
383 return diffName;
384}
385
tomhudson@google.com7d042802011-07-14 13:15:55 +0000386/// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo-actual.png"
387static SkString chrome_expected_path_to_actual (const SkString& expected) {
388 SkString actualPath (expected);
389 actualPath.remove(actualPath.size() - 13, 13);
390 actualPath.append("-actual.png");
391 return actualPath;
392}
393
394/// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo.png"
395static SkString chrome_expected_name_to_short (const SkString& expected) {
396 SkString shortName (expected);
397 shortName.remove(shortName.size() - 13, 13);
398 shortName.append(".png");
399 return shortName;
400}
401
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000402static void release_bitmaps(DiffRecord* drp) {
403 delete drp->fBaseBitmap;
404 drp->fBaseBitmap = NULL;
405 delete drp->fComparisonBitmap;
406 drp->fComparisonBitmap = NULL;
407 delete drp->fDifferenceBitmap;
408 drp->fDifferenceBitmap = NULL;
409}
410
tomhudson@google.com7d042802011-07-14 13:15:55 +0000411
412static void create_and_write_diff_image(DiffRecord* drp,
413 DiffMetricProc dmp,
414 const int colorThreshold,
415 const SkString& outputDir,
416 const SkString& filename) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000417 const int w = drp->fBaseWidth;
418 const int h = drp->fBaseHeight;
419 drp->fDifferenceBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
420 drp->fDifferenceBitmap->allocPixels();
tomhudson@google.com4e305982011-07-13 17:42:46 +0000421 compute_diff(drp, dmp, colorThreshold);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000422
423 SkString outPath (outputDir);
424 outPath.append(filename_to_diff_filename(filename));
425 write_bitmap(outPath, drp->fDifferenceBitmap);
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000426 release_bitmaps(drp);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000427}
428
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000429/// Creates difference images, returns the number that have a 0 metric.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000430static void create_diff_images (DiffMetricProc dmp,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000431 const int colorThreshold,
432 RecordArray* differences,
433 const SkString& baseDir,
434 const SkString& comparisonDir,
435 const SkString& outputDir,
436 DiffSummary* summary) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000437
438 //@todo thudson 19 Apr 2011
439 // this lets us know about files in baseDir not in compareDir, but it
440 // doesn't detect files in compareDir not in baseDir. Doing that
441 // efficiently seems to imply iterating through both directories to
442 // create a merged list, and then attempting to process every entry
443 // in that list?
444
445 SkOSFile::Iter baseIterator (baseDir.c_str());
446 SkString filename;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000447 while (baseIterator.next(&filename)) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000448 if (filename.endsWith(".pdf")) {
449 continue;
450 }
tomhudson@google.com4e305982011-07-13 17:42:46 +0000451 SkString basePath (baseDir);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000452 basePath.append(filename);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000453 SkString comparisonPath (comparisonDir);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000454 comparisonPath.append(filename);
455 DiffRecord * drp = new DiffRecord (filename, basePath, comparisonPath);
456 if (!get_bitmaps(drp)) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000457 continue;
458 }
459
tomhudson@google.com7d042802011-07-14 13:15:55 +0000460 create_and_write_diff_image(drp, dmp, colorThreshold,
461 outputDir, filename);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000462
463 differences->push(drp);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000464 summary->add(drp);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000465 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000466}
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000467
tomhudson@google.com7d042802011-07-14 13:15:55 +0000468static void create_diff_images_chromium (DiffMetricProc dmp,
469 const int colorThreshold,
470 RecordArray* differences,
471 const SkString& dirname,
472 const SkString& outputDir,
473 DiffSummary* summary) {
474 SkOSFile::Iter baseIterator (dirname.c_str());
475 SkString filename;
476 while (baseIterator.next(&filename)) {
477 if (filename.endsWith(".pdf")) {
478 continue;
479 }
480 if (filename.endsWith("-expected.png")) {
481 SkString expectedPath (dirname);
482 expectedPath.append(filename);
483 SkString shortName (chrome_expected_name_to_short(filename));
484 SkString actualPath (chrome_expected_path_to_actual(expectedPath));
485 DiffRecord * drp =
486 new DiffRecord (shortName, expectedPath, actualPath);
487 if (!get_bitmaps(drp)) {
488 continue;
489 }
490 create_and_write_diff_image(drp, dmp, colorThreshold,
491 outputDir, shortName);
492
493 differences->push(drp);
494 summary->add(drp);
495 }
496 }
497}
498
499static void analyze_chromium(DiffMetricProc dmp,
500 const int colorThreshold,
501 RecordArray* differences,
502 const SkString& dirname,
503 const SkString& outputDir,
504 DiffSummary* summary) {
505 create_diff_images_chromium(dmp, colorThreshold, differences,
506 dirname, outputDir, summary);
507 SkOSFile::Iter dirIterator(dirname.c_str());
508 SkString newdirname;
509 while (dirIterator.next(&newdirname, true)) {
510 if (newdirname.startsWith(".")) {
511 continue;
512 }
513 SkString fullname (dirname);
514 fullname.append(newdirname);
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000515 if (!fullname.endsWith(PATH_DIV_STR)) {
516 fullname.append(PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000517 }
518 analyze_chromium(dmp, colorThreshold, differences,
519 fullname, outputDir, summary);
520 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000521}
522
523/// Make layout more consistent by scaling image to 240 height, 360 width,
524/// or natural size, whichever is smallest.
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000525static int compute_image_height (int height, int width) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000526 int retval = 240;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000527 if (height < retval) {
528 retval = height;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000529 }
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000530 float scale = (float) retval / height;
531 if (width * scale > 360) {
532 scale = (float) 360 / width;
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000533 retval = static_cast<int>(height * scale);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000534 }
535 return retval;
536}
537
538static void print_page_header (SkFILEWStream* stream,
539 const int matchCount,
540 const int colorThreshold,
541 const RecordArray& differences) {
542 SkTime::DateTime dt;
543 SkTime::GetDateTime(&dt);
544 stream->writeText("SkDiff run at ");
545 stream->writeDecAsText(dt.fHour);
546 stream->writeText(":");
547 if (dt.fMinute < 10) {
548 stream->writeText("0");
549 }
550 stream->writeDecAsText(dt.fMinute);
551 stream->writeText(":");
552 if (dt.fSecond < 10) {
553 stream->writeText("0");
554 }
555 stream->writeDecAsText(dt.fSecond);
556 stream->writeText("<br>");
557 stream->writeDecAsText(matchCount);
558 stream->writeText(" of ");
559 stream->writeDecAsText(differences.count());
560 stream->writeText(" images matched ");
561 if (colorThreshold == 0) {
562 stream->writeText("exactly");
563 } else {
564 stream->writeText("within ");
565 stream->writeDecAsText(colorThreshold);
566 stream->writeText(" color units per component");
567 }
568 stream->writeText(".<br>");
569
570}
571
572static void print_pixel_count (SkFILEWStream* stream,
573 const DiffRecord& diff) {
574 stream->writeText("<br>(");
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000575 stream->writeDecAsText(static_cast<int>(diff.fFractionDifference *
576 diff.fBaseWidth *
577 diff.fBaseHeight));
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000578 stream->writeText(" pixels)");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000579/*
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000580 stream->writeDecAsText(diff.fWeightedFraction *
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000581 diff.fBaseWidth *
582 diff.fBaseHeight);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000583 stream->writeText(" weighted pixels)");
584*/
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000585}
586
587static void print_label_cell (SkFILEWStream* stream,
588 const DiffRecord& diff) {
589 stream->writeText("<td>");
590 stream->writeText(diff.fFilename.c_str());
591 stream->writeText("<br>");
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000592 if (diff.fDoImageSizesMismatch) {
593 stream->writeText("Image sizes differ");
594 stream->writeText("</td>");
595 return;
596 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000597 char metricBuf [20];
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000598 sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000599 stream->writeText(metricBuf);
600 stream->writeText(" of pixels differ");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000601 stream->writeText("\n (");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000602 sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000603 stream->writeText(metricBuf);
604 stream->writeText(" weighted)");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000605 // Write the actual number of pixels that differ if it's < 1%
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000606 if (diff.fFractionDifference < 0.01) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000607 print_pixel_count(stream, diff);
608 }
609 stream->writeText("<br>Average color mismatch ");
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000610 stream->writeDecAsText(static_cast<int>(MAX3(diff.fAverageMismatchR,
611 diff.fAverageMismatchG,
612 diff.fAverageMismatchB)));
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000613 stream->writeText("<br>Max color mismatch ");
614 stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
615 diff.fMaxMismatchG,
616 diff.fMaxMismatchB));
617 stream->writeText("</td>");
618}
619
620static void print_image_cell (SkFILEWStream* stream,
tomhudson@google.com4e305982011-07-13 17:42:46 +0000621 const SkString& path,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000622 int height) {
623 stream->writeText("<td><a href=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000624 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000625 stream->writeText("\"><img src=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000626 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000627 stream->writeText("\" height=\"");
628 stream->writeDecAsText(height);
629 stream->writeText("px\"></a></td>");
630}
631
632static void print_diff_page (const int matchCount,
633 const int colorThreshold,
634 const RecordArray& differences,
635 const SkString& baseDir,
636 const SkString& comparisonDir,
637 const SkString& outputDir) {
638
tomhudson@google.com5b325292011-05-24 19:41:13 +0000639 SkString outputPath (outputDir);
640 outputPath.append("index.html");
641 //SkFILEWStream outputStream ("index.html");
642 SkFILEWStream outputStream (outputPath.c_str());
643
tomhudson@google.com4e305982011-07-13 17:42:46 +0000644 // Need to convert paths from relative-to-cwd to relative-to-outputDir
tomhudson@google.com5b325292011-05-24 19:41:13 +0000645 // FIXME this doesn't work if there are '..' inside the outputDir
646 unsigned int ui;
647 SkString relativePath;
648 for (ui = 0; ui < outputDir.size(); ui++) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000649 if (outputDir[ui] == PATH_DIV_CHAR) {
650 relativePath.append(".." PATH_DIV_STR);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000651 }
652 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000653
654 outputStream.writeText("<html>\n<body>\n");
655 print_page_header(&outputStream, matchCount, colorThreshold, differences);
656
657 outputStream.writeText("<table>\n");
658 int i;
659 for (i = 0; i < differences.count(); i++) {
660 DiffRecord* diff = differences[i];
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000661 if (0 == diff->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000662 continue;
663 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000664 if (!diff->fBasePath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000665 diff->fBasePath.prepend(relativePath);
666 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000667 if (!diff->fComparisonPath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000668 diff->fComparisonPath.prepend(relativePath);
669 }
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000670 int height = compute_image_height(diff->fBaseHeight, diff->fBaseWidth);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000671 outputStream.writeText("<tr>\n");
672 print_label_cell(&outputStream, *diff);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000673 print_image_cell(&outputStream, diff->fBasePath, height);
674 print_image_cell(&outputStream,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000675 filename_to_diff_filename(diff->fFilename), height);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000676 print_image_cell(&outputStream, diff->fComparisonPath, height);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000677 outputStream.writeText("</tr>\n");
678 outputStream.flush();
679 }
680 outputStream.writeText("</table>\n");
681 outputStream.writeText("</body>\n</html>\n");
682 outputStream.flush();
683}
684
685static void usage (char * argv0) {
686 SkDebugf("Skia baseline image diff tool\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000687 SkDebugf("Usage: %s baseDir comparisonDir [outputDir]\n", argv0);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000688 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000689" %s --chromium --release|--debug baseDir outputDir\n", argv0);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000690 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000691" --white: force all difference pixels to white\n"
692" --threshold n: only report differences > n (in one channel) [default 0]\n"
693" --sortbymismatch: sort by average color channel mismatch\n");
694 SkDebugf(
695" --sortbymaxmismatch: sort by worst color channel mismatch,\n"
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000696" break ties with -sortbymismatch,\n"
697" [default by fraction of pixels mismatching]\n");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000698 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000699" --weighted: sort by # pixels different weighted by color difference\n");
700 SkDebugf(
701" --chromium-release: process Webkit LayoutTests results instead of gm\n"
702" --chromium-debug: process Webkit LayoutTests results instead of gm\n");
703 SkDebugf(
704" baseDir: directory to read baseline images from,\n"
705" or chromium/src directory for --chromium.\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000706 SkDebugf(" comparisonDir: directory to read comparison images from\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000707 SkDebugf(
708" outputDir: directory to write difference images to; defaults to\n"
tomhudson@google.com7d042802011-07-14 13:15:55 +0000709" comparisonDir when not running --chromium\n");
710 SkDebugf("Also creates an \"index.html\" file in the output directory.\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000711}
712
713int main (int argc, char ** argv) {
714 DiffMetricProc diffProc = compute_diff_pmcolor;
715 SkQSortCompareProc sortProc = (SkQSortCompareProc) compare_diff_metrics;
716
717 // Maximum error tolerated in any one color channel in any one pixel before
718 // a difference is reported.
719 int colorThreshold = 0;
720 SkString baseDir;
721 SkString comparisonDir;
722 SkString outputDir;
723
tomhudson@google.com7d042802011-07-14 13:15:55 +0000724 bool analyzeChromium = false;
725 bool chromiumDebug = false;
726 bool chromiumRelease = false;
727
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000728 RecordArray differences;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000729 DiffSummary summary;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000730
731 int i, j;
732 for (i = 1, j = 0; i < argc; i++) {
tomhudson@google.com7d042802011-07-14 13:15:55 +0000733 if (!strcmp(argv[i], "--help")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000734 usage(argv[0]);
735 return 0;
736 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000737 if (!strcmp(argv[i], "--white")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000738 diffProc = compute_diff_white;
739 continue;
740 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000741 if (!strcmp(argv[i], "--sortbymismatch")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000742 sortProc = (SkQSortCompareProc) compare_diff_mean_mismatches;
743 continue;
744 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000745 if (!strcmp(argv[i], "--sortbymaxmismatch")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000746 sortProc = (SkQSortCompareProc) compare_diff_max_mismatches;
747 continue;
748 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000749 if (!strcmp(argv[i], "--weighted")) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000750 sortProc = (SkQSortCompareProc) compare_diff_weighted;
751 continue;
752 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000753 if (!strcmp(argv[i], "--chromium-release")) {
754 analyzeChromium = true;
755 chromiumRelease = true;
756 continue;
757 }
758 if (!strcmp(argv[i], "--chromium-debug")) {
759 analyzeChromium = true;
760 chromiumDebug = true;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000761 continue;
762 }
763 if (argv[i][0] != '-') {
764 switch (j++) {
765 case 0:
766 baseDir.set(argv[i]);
767 continue;
768 case 1:
769 comparisonDir.set(argv[i]);
770 continue;
771 case 2:
772 outputDir.set(argv[i]);
773 continue;
774 default:
775 usage(argv[0]);
776 return 0;
777 }
778 }
779
780 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
781 usage(argv[0]);
782 return 0;
783 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000784 if (analyzeChromium) {
785 if (j != 2) {
786 usage(argv[0]);
787 return 0;
788 }
789 if (chromiumRelease && chromiumDebug) {
790 SkDebugf(
791"--chromium must be either -release or -debug, not both!\n");
792 return 0;
793 }
794 }
795
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000796 if (j == 2) {
797 outputDir = comparisonDir;
798 } else if (j != 3) {
799 usage(argv[0]);
800 return 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000801 }
802
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000803 if (!baseDir.endsWith(PATH_DIV_STR)) {
804 baseDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000805 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000806 if (!comparisonDir.endsWith(PATH_DIV_STR)) {
807 comparisonDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000808 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000809 if (!outputDir.endsWith(PATH_DIV_STR)) {
810 outputDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000811 }
812
tomhudson@google.com7d042802011-07-14 13:15:55 +0000813 if (analyzeChromium) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000814 baseDir.append("webkit" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000815 if (chromiumRelease) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000816 baseDir.append("Release" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000817 }
818 if (chromiumDebug) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000819 baseDir.append("Debug" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000820 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000821 baseDir.append("layout-test-results" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000822 analyze_chromium(diffProc, colorThreshold, &differences,
823 baseDir, outputDir, &summary);
824 } else {
825 create_diff_images(diffProc, colorThreshold, &differences,
826 baseDir, comparisonDir, outputDir, &summary);
827 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000828 summary.print();
tomhudson@google.com7d042802011-07-14 13:15:55 +0000829
830 if (differences.count()) {
831 SkQSort(differences.begin(), differences.count(),
832 sizeof(DiffRecord*), sortProc);
833 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000834 print_diff_page(summary.fNumMatches, colorThreshold, differences,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000835 baseDir, comparisonDir, outputDir);
836
837 for (i = 0; i < differences.count(); i++) {
838 delete differences[i];
839 }
840}