blob: 6d202a726f4d50dc47f480c08f64629fa45a4260 [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.com4e305982011-07-13 17:42:46 +000062 , fMaxMismatchB (0) {
tomhudson@google.com7d042802011-07-14 13:15:55 +000063 // These asserts are valid for GM, but not for --chromium
64 //SkASSERT(basePath.endsWith(filename.c_str()));
65 //SkASSERT(comparisonPath.endsWith(filename.c_str()));
tomhudson@google.com4e305982011-07-13 17:42:46 +000066 };
tomhudson@google.com4b33d282011-04-27 15:39:30 +000067
68 SkString fFilename;
tomhudson@google.com4e305982011-07-13 17:42:46 +000069 SkString fBasePath;
70 SkString fComparisonPath;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000071
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000072 SkBitmap* fBaseBitmap;
73 SkBitmap* fComparisonBitmap;
74 SkBitmap* fDifferenceBitmap;
75
76 int fBaseHeight;
77 int fBaseWidth;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000078
79 /// Arbitrary floating-point metric to be used to sort images from most
80 /// to least different from baseline; values of 0 will be omitted from the
81 /// summary webpage.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000082 float fFractionDifference;
83 float fWeightedFraction;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000084
85 float fAverageMismatchR;
86 float fAverageMismatchG;
87 float fAverageMismatchB;
88
89 uint32_t fMaxMismatchR;
90 uint32_t fMaxMismatchG;
91 uint32_t fMaxMismatchB;
92};
93
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000094#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
95#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
96
97struct DiffSummary {
98 DiffSummary ()
99 : fNumMatches (0)
100 , fNumMismatches (0)
101 , fMaxMismatchV (0)
102 , fMaxMismatchPercent (0) { };
103
104 uint32_t fNumMatches;
105 uint32_t fNumMismatches;
106 uint32_t fMaxMismatchV;
107 float fMaxMismatchPercent;
108
109 void print () {
110 printf("%d of %d images matched.\n", fNumMatches,
111 fNumMatches + fNumMismatches);
112 if (fNumMismatches > 0) {
113 printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
114 printf("Largest area mismatch was %.2f%% of pixels\n",
115 fMaxMismatchPercent);
116 }
117
118 }
119
120 void add (DiffRecord* drp) {
121 if (0 == drp->fFractionDifference) {
122 fNumMatches++;
123 } else {
124 fNumMismatches++;
125 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
126 fMaxMismatchPercent = drp->fFractionDifference * 100;
127 }
tomhudson@google.com88a0e052011-06-09 18:54:01 +0000128 uint32_t value = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
129 drp->fMaxMismatchB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000130 if (value > fMaxMismatchV) {
131 fMaxMismatchV = value;
132 }
133 }
134 }
135};
136
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000137typedef SkTDArray<DiffRecord*> RecordArray;
138
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000139/// Comparison routine for qsort; sorts by fFractionDifference
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000140/// from largest to smallest.
141static int compare_diff_metrics (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000142 if ((*lhs)->fFractionDifference < (*rhs)->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000143 return 1;
144 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000145 if ((*rhs)->fFractionDifference < (*lhs)->fFractionDifference) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000146 return -1;
147 }
148 return 0;
149}
150
151static int compare_diff_weighted (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000152 if ((*lhs)->fWeightedFraction < (*rhs)->fWeightedFraction) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000153 return 1;
154 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000155 if ((*lhs)->fWeightedFraction > (*rhs)->fWeightedFraction) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000156 return -1;
157 }
158 return 0;
159}
160
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000161/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
162/// from largest to smallest.
163static int compare_diff_mean_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
164 float leftValue = MAX3((*lhs)->fAverageMismatchR,
165 (*lhs)->fAverageMismatchG,
166 (*lhs)->fAverageMismatchB);
167 float rightValue = MAX3((*rhs)->fAverageMismatchR,
168 (*rhs)->fAverageMismatchG,
169 (*rhs)->fAverageMismatchB);
170 if (leftValue < rightValue) {
171 return 1;
172 }
173 if (rightValue < leftValue) {
174 return -1;
175 }
176 return 0;
177}
178
179/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB})
180/// from largest to smallest.
181static int compare_diff_max_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000182 uint32_t leftValue = MAX3((*lhs)->fMaxMismatchR,
183 (*lhs)->fMaxMismatchG,
184 (*lhs)->fMaxMismatchB);
185 uint32_t rightValue = MAX3((*rhs)->fMaxMismatchR,
186 (*rhs)->fMaxMismatchG,
187 (*rhs)->fMaxMismatchB);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000188 if (leftValue < rightValue) {
189 return 1;
190 }
191 if (rightValue < leftValue) {
192 return -1;
193 }
194 return compare_diff_mean_mismatches(lhs, rhs);
195}
196
197
198
199/// Parameterized routine to compute the color of a pixel in a difference image.
200typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
201
tomhudson@google.com4e305982011-07-13 17:42:46 +0000202static bool get_bitmaps (DiffRecord* diffRecord) {
203 SkFILEStream compareStream(diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000204 if (!compareStream.isValid()) {
205 SkDebugf("WARNING: couldn't open comparison file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000206 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000207 return false;
208 }
209
tomhudson@google.com4e305982011-07-13 17:42:46 +0000210 SkFILEStream baseStream(diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000211 if (!baseStream.isValid()) {
212 SkDebugf("ERROR: couldn't open base file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000213 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000214 return false;
215 }
216
217 SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
218 if (NULL == codec) {
219 SkDebugf("ERROR: no codec found for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000220 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000221 return false;
222 }
223
224 SkAutoTDelete<SkImageDecoder> ad(codec);
225
226 baseStream.rewind();
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000227 if (!codec->decode(&baseStream, diffRecord->fBaseBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000228 SkBitmap::kARGB_8888_Config,
229 SkImageDecoder::kDecodePixels_Mode)) {
230 SkDebugf("ERROR: codec failed for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000231 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000232 return false;
233 }
234
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000235 diffRecord->fBaseWidth = diffRecord->fBaseBitmap->width();
236 diffRecord->fBaseHeight = diffRecord->fBaseBitmap->height();
237
238 if (!codec->decode(&compareStream, diffRecord->fComparisonBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000239 SkBitmap::kARGB_8888_Config,
240 SkImageDecoder::kDecodePixels_Mode)) {
241 SkDebugf("ERROR: codec failed for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000242 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000243 return false;
244 }
245
246 return true;
247}
248
249// from gm - thanks to PNG, we need to force all pixels 100% opaque
250static void force_all_opaque(const SkBitmap& bitmap) {
251 SkAutoLockPixels lock(bitmap);
252 for (int y = 0; y < bitmap.height(); y++) {
253 for (int x = 0; x < bitmap.width(); x++) {
254 *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
255 }
256 }
257}
258
259// from gm
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000260static bool write_bitmap(const SkString& path, const SkBitmap* bitmap) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000261 SkBitmap copy;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000262 bitmap->copyTo(&copy, SkBitmap::kARGB_8888_Config);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000263 force_all_opaque(copy);
264 return SkImageEncoder::EncodeFile(path.c_str(), copy,
265 SkImageEncoder::kPNG_Type, 100);
266}
267
268// from gm
269static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
270 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
271 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
272 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
273
274 return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
275}
276
277/// Returns white on every pixel so that differences jump out at you;
278/// makes it easy to spot areas of difference that are in the least-significant
279/// bits.
280static inline SkPMColor compute_diff_white(SkPMColor c0, SkPMColor c1) {
281 return SkPackARGB32(0xFF, 0xFF, 0xFF, 0xFF);
282}
283
284static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1,
285 const int threshold) {
286 int da = SkGetPackedA32(c0) - SkGetPackedA32(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 ((SkAbs32(da) <= threshold) &&
292 (SkAbs32(dr) <= threshold) &&
293 (SkAbs32(dg) <= threshold) &&
294 (SkAbs32(db) <= threshold));
295}
296
297// based on gm
298static void compute_diff(DiffRecord* dr,
299 DiffMetricProc diffFunction,
300 const int colorThreshold) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000301 SkAutoLockPixels alp(*dr->fDifferenceBitmap);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000302
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000303 const int w = dr->fComparisonBitmap->width();
304 const int h = dr->fComparisonBitmap->height();
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000305 int mismatchedPixels = 0;
306 int totalMismatchR = 0;
307 int totalMismatchG = 0;
308 int totalMismatchB = 0;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000309 // Accumulate fractionally different pixels, then divide out
310 // # of pixels at the end.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000311 dr->fWeightedFraction = 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000312 for (int y = 0; y < h; y++) {
313 for (int x = 0; x < w; x++) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000314 SkPMColor c0 = *dr->fBaseBitmap->getAddr32(x, y);
315 SkPMColor c1 = *dr->fComparisonBitmap->getAddr32(x, y);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000316 SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
317 SkPMColor outputDifference = diffFunction(c0, c1);
318 uint32_t thisR = SkGetPackedR32(trueDifference);
319 uint32_t thisG = SkGetPackedG32(trueDifference);
320 uint32_t thisB = SkGetPackedB32(trueDifference);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000321 totalMismatchR += thisR;
322 totalMismatchG += thisG;
323 totalMismatchB += thisB;
324 // In HSV, value is defined as max RGB component.
325 int value = MAX3(thisR, thisG, thisB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000326 dr->fWeightedFraction += ((float) value) / 255;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000327 if (thisR > dr->fMaxMismatchR) {
328 dr->fMaxMismatchR = thisR;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000329 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000330 if (thisG > dr->fMaxMismatchG) {
331 dr->fMaxMismatchG = thisG;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000332 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000333 if (thisB > dr->fMaxMismatchB) {
334 dr->fMaxMismatchB = thisB;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000335 }
336 if (!colors_match_thresholded(c0, c1, colorThreshold)) {
337 mismatchedPixels++;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000338 *dr->fDifferenceBitmap->getAddr32(x, y) = outputDifference;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000339 } else {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000340 *dr->fDifferenceBitmap->getAddr32(x, y) = 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000341 }
342 }
343 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000344 int pixelCount = w * h;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000345 dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
346 dr->fWeightedFraction /= pixelCount;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000347 dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
348 dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
349 dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000350}
351
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000352/// Given a image filename, returns the name of the file containing the
353/// associated difference image.
354static SkString filename_to_diff_filename (const SkString& filename) {
355 SkString diffName (filename);
356 const char* cstring = diffName.c_str();
357 int dotOffset = strrchr(cstring, '.') - cstring;
358 diffName.remove(dotOffset, diffName.size() - dotOffset);
359 diffName.append("-diff.png");
360 return diffName;
361}
362
tomhudson@google.com7d042802011-07-14 13:15:55 +0000363/// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo-actual.png"
364static SkString chrome_expected_path_to_actual (const SkString& expected) {
365 SkString actualPath (expected);
366 actualPath.remove(actualPath.size() - 13, 13);
367 actualPath.append("-actual.png");
368 return actualPath;
369}
370
371/// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo.png"
372static SkString chrome_expected_name_to_short (const SkString& expected) {
373 SkString shortName (expected);
374 shortName.remove(shortName.size() - 13, 13);
375 shortName.append(".png");
376 return shortName;
377}
378
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000379static void release_bitmaps(DiffRecord* drp) {
380 delete drp->fBaseBitmap;
381 drp->fBaseBitmap = NULL;
382 delete drp->fComparisonBitmap;
383 drp->fComparisonBitmap = NULL;
384 delete drp->fDifferenceBitmap;
385 drp->fDifferenceBitmap = NULL;
386}
387
tomhudson@google.com7d042802011-07-14 13:15:55 +0000388
389static void create_and_write_diff_image(DiffRecord* drp,
390 DiffMetricProc dmp,
391 const int colorThreshold,
392 const SkString& outputDir,
393 const SkString& filename) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000394 const int w = drp->fBaseWidth;
395 const int h = drp->fBaseHeight;
396 drp->fDifferenceBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
397 drp->fDifferenceBitmap->allocPixels();
tomhudson@google.com4e305982011-07-13 17:42:46 +0000398 compute_diff(drp, dmp, colorThreshold);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000399
400 SkString outPath (outputDir);
401 outPath.append(filename_to_diff_filename(filename));
402 write_bitmap(outPath, drp->fDifferenceBitmap);
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000403 release_bitmaps(drp);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000404}
405
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000406/// Creates difference images, returns the number that have a 0 metric.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000407static void create_diff_images (DiffMetricProc dmp,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000408 const int colorThreshold,
409 RecordArray* differences,
410 const SkString& baseDir,
411 const SkString& comparisonDir,
412 const SkString& outputDir,
413 DiffSummary* summary) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000414
415 //@todo thudson 19 Apr 2011
416 // this lets us know about files in baseDir not in compareDir, but it
417 // doesn't detect files in compareDir not in baseDir. Doing that
418 // efficiently seems to imply iterating through both directories to
419 // create a merged list, and then attempting to process every entry
420 // in that list?
421
422 SkOSFile::Iter baseIterator (baseDir.c_str());
423 SkString filename;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000424 while (baseIterator.next(&filename)) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000425 if (filename.endsWith(".pdf")) {
426 continue;
427 }
tomhudson@google.com4e305982011-07-13 17:42:46 +0000428 SkString basePath (baseDir);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000429 basePath.append(filename);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000430 SkString comparisonPath (comparisonDir);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000431 comparisonPath.append(filename);
432 DiffRecord * drp = new DiffRecord (filename, basePath, comparisonPath);
433 if (!get_bitmaps(drp)) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000434 continue;
435 }
436
tomhudson@google.com7d042802011-07-14 13:15:55 +0000437 create_and_write_diff_image(drp, dmp, colorThreshold,
438 outputDir, filename);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000439
440 differences->push(drp);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000441 summary->add(drp);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000442 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000443}
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000444
tomhudson@google.com7d042802011-07-14 13:15:55 +0000445static void create_diff_images_chromium (DiffMetricProc dmp,
446 const int colorThreshold,
447 RecordArray* differences,
448 const SkString& dirname,
449 const SkString& outputDir,
450 DiffSummary* summary) {
451 SkOSFile::Iter baseIterator (dirname.c_str());
452 SkString filename;
453 while (baseIterator.next(&filename)) {
454 if (filename.endsWith(".pdf")) {
455 continue;
456 }
457 if (filename.endsWith("-expected.png")) {
458 SkString expectedPath (dirname);
459 expectedPath.append(filename);
460 SkString shortName (chrome_expected_name_to_short(filename));
461 SkString actualPath (chrome_expected_path_to_actual(expectedPath));
462 DiffRecord * drp =
463 new DiffRecord (shortName, expectedPath, actualPath);
464 if (!get_bitmaps(drp)) {
465 continue;
466 }
467 create_and_write_diff_image(drp, dmp, colorThreshold,
468 outputDir, shortName);
469
470 differences->push(drp);
471 summary->add(drp);
472 }
473 }
474}
475
476static void analyze_chromium(DiffMetricProc dmp,
477 const int colorThreshold,
478 RecordArray* differences,
479 const SkString& dirname,
480 const SkString& outputDir,
481 DiffSummary* summary) {
482 create_diff_images_chromium(dmp, colorThreshold, differences,
483 dirname, outputDir, summary);
484 SkOSFile::Iter dirIterator(dirname.c_str());
485 SkString newdirname;
486 while (dirIterator.next(&newdirname, true)) {
487 if (newdirname.startsWith(".")) {
488 continue;
489 }
490 SkString fullname (dirname);
491 fullname.append(newdirname);
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000492 if (!fullname.endsWith(PATH_DIV_STR)) {
493 fullname.append(PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000494 }
495 analyze_chromium(dmp, colorThreshold, differences,
496 fullname, outputDir, summary);
497 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000498}
499
500/// Make layout more consistent by scaling image to 240 height, 360 width,
501/// or natural size, whichever is smallest.
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000502static int compute_image_height (int height, int width) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000503 int retval = 240;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000504 if (height < retval) {
505 retval = height;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000506 }
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000507 float scale = (float) retval / height;
508 if (width * scale > 360) {
509 scale = (float) 360 / width;
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000510 retval = static_cast<int>(height * scale);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000511 }
512 return retval;
513}
514
515static void print_page_header (SkFILEWStream* stream,
516 const int matchCount,
517 const int colorThreshold,
518 const RecordArray& differences) {
519 SkTime::DateTime dt;
520 SkTime::GetDateTime(&dt);
521 stream->writeText("SkDiff run at ");
522 stream->writeDecAsText(dt.fHour);
523 stream->writeText(":");
524 if (dt.fMinute < 10) {
525 stream->writeText("0");
526 }
527 stream->writeDecAsText(dt.fMinute);
528 stream->writeText(":");
529 if (dt.fSecond < 10) {
530 stream->writeText("0");
531 }
532 stream->writeDecAsText(dt.fSecond);
533 stream->writeText("<br>");
534 stream->writeDecAsText(matchCount);
535 stream->writeText(" of ");
536 stream->writeDecAsText(differences.count());
537 stream->writeText(" images matched ");
538 if (colorThreshold == 0) {
539 stream->writeText("exactly");
540 } else {
541 stream->writeText("within ");
542 stream->writeDecAsText(colorThreshold);
543 stream->writeText(" color units per component");
544 }
545 stream->writeText(".<br>");
546
547}
548
549static void print_pixel_count (SkFILEWStream* stream,
550 const DiffRecord& diff) {
551 stream->writeText("<br>(");
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000552 stream->writeDecAsText(static_cast<int>(diff.fFractionDifference *
553 diff.fBaseWidth *
554 diff.fBaseHeight));
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000555 stream->writeText(" pixels)");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000556/*
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000557 stream->writeDecAsText(diff.fWeightedFraction *
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000558 diff.fBaseWidth *
559 diff.fBaseHeight);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000560 stream->writeText(" weighted pixels)");
561*/
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000562}
563
564static void print_label_cell (SkFILEWStream* stream,
565 const DiffRecord& diff) {
566 stream->writeText("<td>");
567 stream->writeText(diff.fFilename.c_str());
568 stream->writeText("<br>");
569 char metricBuf [20];
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000570 sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000571 stream->writeText(metricBuf);
572 stream->writeText(" of pixels differ");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000573 stream->writeText("\n (");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000574 sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000575 stream->writeText(metricBuf);
576 stream->writeText(" weighted)");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000577 // Write the actual number of pixels that differ if it's < 1%
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000578 if (diff.fFractionDifference < 0.01) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000579 print_pixel_count(stream, diff);
580 }
581 stream->writeText("<br>Average color mismatch ");
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000582 stream->writeDecAsText(static_cast<int>(MAX3(diff.fAverageMismatchR,
583 diff.fAverageMismatchG,
584 diff.fAverageMismatchB)));
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000585 stream->writeText("<br>Max color mismatch ");
586 stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
587 diff.fMaxMismatchG,
588 diff.fMaxMismatchB));
589 stream->writeText("</td>");
590}
591
592static void print_image_cell (SkFILEWStream* stream,
tomhudson@google.com4e305982011-07-13 17:42:46 +0000593 const SkString& path,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000594 int height) {
595 stream->writeText("<td><a href=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000596 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000597 stream->writeText("\"><img src=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000598 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000599 stream->writeText("\" height=\"");
600 stream->writeDecAsText(height);
601 stream->writeText("px\"></a></td>");
602}
603
604static void print_diff_page (const int matchCount,
605 const int colorThreshold,
606 const RecordArray& differences,
607 const SkString& baseDir,
608 const SkString& comparisonDir,
609 const SkString& outputDir) {
610
tomhudson@google.com5b325292011-05-24 19:41:13 +0000611 SkString outputPath (outputDir);
612 outputPath.append("index.html");
613 //SkFILEWStream outputStream ("index.html");
614 SkFILEWStream outputStream (outputPath.c_str());
615
tomhudson@google.com4e305982011-07-13 17:42:46 +0000616 // Need to convert paths from relative-to-cwd to relative-to-outputDir
tomhudson@google.com5b325292011-05-24 19:41:13 +0000617 // FIXME this doesn't work if there are '..' inside the outputDir
618 unsigned int ui;
619 SkString relativePath;
620 for (ui = 0; ui < outputDir.size(); ui++) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000621 if (outputDir[ui] == PATH_DIV_CHAR) {
622 relativePath.append(".." PATH_DIV_STR);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000623 }
624 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000625
626 outputStream.writeText("<html>\n<body>\n");
627 print_page_header(&outputStream, matchCount, colorThreshold, differences);
628
629 outputStream.writeText("<table>\n");
630 int i;
631 for (i = 0; i < differences.count(); i++) {
632 DiffRecord* diff = differences[i];
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000633 if (0 == diff->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000634 continue;
635 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000636 if (!diff->fBasePath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000637 diff->fBasePath.prepend(relativePath);
638 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000639 if (!diff->fComparisonPath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000640 diff->fComparisonPath.prepend(relativePath);
641 }
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000642 int height = compute_image_height(diff->fBaseHeight, diff->fBaseWidth);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000643 outputStream.writeText("<tr>\n");
644 print_label_cell(&outputStream, *diff);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000645 print_image_cell(&outputStream, diff->fBasePath, height);
646 print_image_cell(&outputStream,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000647 filename_to_diff_filename(diff->fFilename), height);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000648 print_image_cell(&outputStream, diff->fComparisonPath, height);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000649 outputStream.writeText("</tr>\n");
650 outputStream.flush();
651 }
652 outputStream.writeText("</table>\n");
653 outputStream.writeText("</body>\n</html>\n");
654 outputStream.flush();
655}
656
657static void usage (char * argv0) {
658 SkDebugf("Skia baseline image diff tool\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000659 SkDebugf("Usage: %s baseDir comparisonDir [outputDir]\n", argv0);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000660 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000661" %s --chromium --release|--debug baseDir outputDir\n", argv0);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000662 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000663" --white: force all difference pixels to white\n"
664" --threshold n: only report differences > n (in one channel) [default 0]\n"
665" --sortbymismatch: sort by average color channel mismatch\n");
666 SkDebugf(
667" --sortbymaxmismatch: sort by worst color channel mismatch,\n"
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000668" break ties with -sortbymismatch,\n"
669" [default by fraction of pixels mismatching]\n");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000670 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000671" --weighted: sort by # pixels different weighted by color difference\n");
672 SkDebugf(
673" --chromium-release: process Webkit LayoutTests results instead of gm\n"
674" --chromium-debug: process Webkit LayoutTests results instead of gm\n");
675 SkDebugf(
676" baseDir: directory to read baseline images from,\n"
677" or chromium/src directory for --chromium.\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000678 SkDebugf(" comparisonDir: directory to read comparison images from\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000679 SkDebugf(
680" outputDir: directory to write difference images to; defaults to\n"
tomhudson@google.com7d042802011-07-14 13:15:55 +0000681" comparisonDir when not running --chromium\n");
682 SkDebugf("Also creates an \"index.html\" file in the output directory.\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000683}
684
685int main (int argc, char ** argv) {
686 DiffMetricProc diffProc = compute_diff_pmcolor;
687 SkQSortCompareProc sortProc = (SkQSortCompareProc) compare_diff_metrics;
688
689 // Maximum error tolerated in any one color channel in any one pixel before
690 // a difference is reported.
691 int colorThreshold = 0;
692 SkString baseDir;
693 SkString comparisonDir;
694 SkString outputDir;
695
tomhudson@google.com7d042802011-07-14 13:15:55 +0000696 bool analyzeChromium = false;
697 bool chromiumDebug = false;
698 bool chromiumRelease = false;
699
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000700 RecordArray differences;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000701 DiffSummary summary;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000702
703 int i, j;
704 for (i = 1, j = 0; i < argc; i++) {
tomhudson@google.com7d042802011-07-14 13:15:55 +0000705 if (!strcmp(argv[i], "--help")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000706 usage(argv[0]);
707 return 0;
708 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000709 if (!strcmp(argv[i], "--white")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000710 diffProc = compute_diff_white;
711 continue;
712 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000713 if (!strcmp(argv[i], "--sortbymismatch")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000714 sortProc = (SkQSortCompareProc) compare_diff_mean_mismatches;
715 continue;
716 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000717 if (!strcmp(argv[i], "--sortbymaxmismatch")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000718 sortProc = (SkQSortCompareProc) compare_diff_max_mismatches;
719 continue;
720 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000721 if (!strcmp(argv[i], "--weighted")) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000722 sortProc = (SkQSortCompareProc) compare_diff_weighted;
723 continue;
724 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000725 if (!strcmp(argv[i], "--chromium-release")) {
726 analyzeChromium = true;
727 chromiumRelease = true;
728 continue;
729 }
730 if (!strcmp(argv[i], "--chromium-debug")) {
731 analyzeChromium = true;
732 chromiumDebug = true;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000733 continue;
734 }
735 if (argv[i][0] != '-') {
736 switch (j++) {
737 case 0:
738 baseDir.set(argv[i]);
739 continue;
740 case 1:
741 comparisonDir.set(argv[i]);
742 continue;
743 case 2:
744 outputDir.set(argv[i]);
745 continue;
746 default:
747 usage(argv[0]);
748 return 0;
749 }
750 }
751
752 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
753 usage(argv[0]);
754 return 0;
755 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000756 if (analyzeChromium) {
757 if (j != 2) {
758 usage(argv[0]);
759 return 0;
760 }
761 if (chromiumRelease && chromiumDebug) {
762 SkDebugf(
763"--chromium must be either -release or -debug, not both!\n");
764 return 0;
765 }
766 }
767
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000768 if (j == 2) {
769 outputDir = comparisonDir;
770 } else if (j != 3) {
771 usage(argv[0]);
772 return 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000773 }
774
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000775 if (!baseDir.endsWith(PATH_DIV_STR)) {
776 baseDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000777 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000778 if (!comparisonDir.endsWith(PATH_DIV_STR)) {
779 comparisonDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000780 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000781 if (!outputDir.endsWith(PATH_DIV_STR)) {
782 outputDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000783 }
784
tomhudson@google.com7d042802011-07-14 13:15:55 +0000785 if (analyzeChromium) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000786 baseDir.append("webkit" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000787 if (chromiumRelease) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000788 baseDir.append("Release" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000789 }
790 if (chromiumDebug) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000791 baseDir.append("Debug" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000792 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000793 baseDir.append("layout-test-results" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000794 analyze_chromium(diffProc, colorThreshold, &differences,
795 baseDir, outputDir, &summary);
796 } else {
797 create_diff_images(diffProc, colorThreshold, &differences,
798 baseDir, comparisonDir, outputDir, &summary);
799 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000800 summary.print();
tomhudson@google.com7d042802011-07-14 13:15:55 +0000801
802 if (differences.count()) {
803 SkQSort(differences.begin(), differences.count(),
804 sizeof(DiffRecord*), sortProc);
805 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000806 print_diff_page(summary.fNumMatches, colorThreshold, differences,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000807 baseDir, comparisonDir, outputDir);
808
809 for (i = 0; i < differences.count(); i++) {
810 delete differences[i];
811 }
812}