blob: a024a76626055e7d9263c33d9f96266a52751ae2 [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
35struct DiffRecord {
tomhudson@google.com4e305982011-07-13 17:42:46 +000036 DiffRecord (const SkString filename,
37 const SkString basePath,
38 const SkString comparisonPath)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000039 : fFilename (filename)
tomhudson@google.com4e305982011-07-13 17:42:46 +000040 , fBasePath (basePath)
41 , fComparisonPath (comparisonPath)
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000042 , fBaseBitmap (new SkBitmap ())
43 , fComparisonBitmap (new SkBitmap ())
44 , fDifferenceBitmap (new SkBitmap ())
45 , fBaseHeight (0)
46 , fBaseWidth (0)
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000047 , fFractionDifference (0)
48 , fWeightedFraction (0)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000049 , fAverageMismatchR (0)
50 , fAverageMismatchG (0)
51 , fAverageMismatchB (0)
52 , fMaxMismatchR (0)
53 , fMaxMismatchG (0)
tomhudson@google.com4e305982011-07-13 17:42:46 +000054 , fMaxMismatchB (0) {
tomhudson@google.com7d042802011-07-14 13:15:55 +000055 // These asserts are valid for GM, but not for --chromium
56 //SkASSERT(basePath.endsWith(filename.c_str()));
57 //SkASSERT(comparisonPath.endsWith(filename.c_str()));
tomhudson@google.com4e305982011-07-13 17:42:46 +000058 };
tomhudson@google.com4b33d282011-04-27 15:39:30 +000059
60 SkString fFilename;
tomhudson@google.com4e305982011-07-13 17:42:46 +000061 SkString fBasePath;
62 SkString fComparisonPath;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000063
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000064 SkBitmap* fBaseBitmap;
65 SkBitmap* fComparisonBitmap;
66 SkBitmap* fDifferenceBitmap;
67
68 int fBaseHeight;
69 int fBaseWidth;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000070
71 /// Arbitrary floating-point metric to be used to sort images from most
72 /// to least different from baseline; values of 0 will be omitted from the
73 /// summary webpage.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000074 float fFractionDifference;
75 float fWeightedFraction;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000076
77 float fAverageMismatchR;
78 float fAverageMismatchG;
79 float fAverageMismatchB;
80
81 uint32_t fMaxMismatchR;
82 uint32_t fMaxMismatchG;
83 uint32_t fMaxMismatchB;
84};
85
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000086#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
87#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
88
89struct DiffSummary {
90 DiffSummary ()
91 : fNumMatches (0)
92 , fNumMismatches (0)
93 , fMaxMismatchV (0)
94 , fMaxMismatchPercent (0) { };
95
96 uint32_t fNumMatches;
97 uint32_t fNumMismatches;
98 uint32_t fMaxMismatchV;
99 float fMaxMismatchPercent;
100
101 void print () {
102 printf("%d of %d images matched.\n", fNumMatches,
103 fNumMatches + fNumMismatches);
104 if (fNumMismatches > 0) {
105 printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
106 printf("Largest area mismatch was %.2f%% of pixels\n",
107 fMaxMismatchPercent);
108 }
109
110 }
111
112 void add (DiffRecord* drp) {
113 if (0 == drp->fFractionDifference) {
114 fNumMatches++;
115 } else {
116 fNumMismatches++;
117 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
118 fMaxMismatchPercent = drp->fFractionDifference * 100;
119 }
tomhudson@google.com88a0e052011-06-09 18:54:01 +0000120 uint32_t value = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
121 drp->fMaxMismatchB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000122 if (value > fMaxMismatchV) {
123 fMaxMismatchV = value;
124 }
125 }
126 }
127};
128
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000129typedef SkTDArray<DiffRecord*> RecordArray;
130
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000131/// Comparison routine for qsort; sorts by fFractionDifference
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000132/// from largest to smallest.
133static int compare_diff_metrics (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000134 if ((*lhs)->fFractionDifference < (*rhs)->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000135 return 1;
136 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000137 if ((*rhs)->fFractionDifference < (*lhs)->fFractionDifference) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000138 return -1;
139 }
140 return 0;
141}
142
143static int compare_diff_weighted (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000144 if ((*lhs)->fWeightedFraction < (*rhs)->fWeightedFraction) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000145 return 1;
146 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000147 if ((*lhs)->fWeightedFraction > (*rhs)->fWeightedFraction) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000148 return -1;
149 }
150 return 0;
151}
152
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000153/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
154/// from largest to smallest.
155static int compare_diff_mean_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
156 float leftValue = MAX3((*lhs)->fAverageMismatchR,
157 (*lhs)->fAverageMismatchG,
158 (*lhs)->fAverageMismatchB);
159 float rightValue = MAX3((*rhs)->fAverageMismatchR,
160 (*rhs)->fAverageMismatchG,
161 (*rhs)->fAverageMismatchB);
162 if (leftValue < rightValue) {
163 return 1;
164 }
165 if (rightValue < leftValue) {
166 return -1;
167 }
168 return 0;
169}
170
171/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB})
172/// from largest to smallest.
173static int compare_diff_max_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
174 float leftValue = MAX3((*lhs)->fMaxMismatchR,
175 (*lhs)->fMaxMismatchG,
176 (*lhs)->fMaxMismatchB);
177 float rightValue = MAX3((*rhs)->fMaxMismatchR,
178 (*rhs)->fMaxMismatchG,
179 (*rhs)->fMaxMismatchB);
180 if (leftValue < rightValue) {
181 return 1;
182 }
183 if (rightValue < leftValue) {
184 return -1;
185 }
186 return compare_diff_mean_mismatches(lhs, rhs);
187}
188
189
190
191/// Parameterized routine to compute the color of a pixel in a difference image.
192typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
193
tomhudson@google.com4e305982011-07-13 17:42:46 +0000194static bool get_bitmaps (DiffRecord* diffRecord) {
195 SkFILEStream compareStream(diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000196 if (!compareStream.isValid()) {
197 SkDebugf("WARNING: couldn't open comparison file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000198 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000199 return false;
200 }
201
tomhudson@google.com4e305982011-07-13 17:42:46 +0000202 SkFILEStream baseStream(diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000203 if (!baseStream.isValid()) {
204 SkDebugf("ERROR: couldn't open base file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000205 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000206 return false;
207 }
208
209 SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
210 if (NULL == codec) {
211 SkDebugf("ERROR: no codec found for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000212 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000213 return false;
214 }
215
216 SkAutoTDelete<SkImageDecoder> ad(codec);
217
218 baseStream.rewind();
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000219 if (!codec->decode(&baseStream, diffRecord->fBaseBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000220 SkBitmap::kARGB_8888_Config,
221 SkImageDecoder::kDecodePixels_Mode)) {
222 SkDebugf("ERROR: codec failed for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000223 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000224 return false;
225 }
226
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000227 diffRecord->fBaseWidth = diffRecord->fBaseBitmap->width();
228 diffRecord->fBaseHeight = diffRecord->fBaseBitmap->height();
229
230 if (!codec->decode(&compareStream, diffRecord->fComparisonBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000231 SkBitmap::kARGB_8888_Config,
232 SkImageDecoder::kDecodePixels_Mode)) {
233 SkDebugf("ERROR: codec failed for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000234 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000235 return false;
236 }
237
238 return true;
239}
240
241// from gm - thanks to PNG, we need to force all pixels 100% opaque
242static void force_all_opaque(const SkBitmap& bitmap) {
243 SkAutoLockPixels lock(bitmap);
244 for (int y = 0; y < bitmap.height(); y++) {
245 for (int x = 0; x < bitmap.width(); x++) {
246 *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
247 }
248 }
249}
250
251// from gm
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000252static bool write_bitmap(const SkString& path, const SkBitmap* bitmap) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000253 SkBitmap copy;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000254 bitmap->copyTo(&copy, SkBitmap::kARGB_8888_Config);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000255 force_all_opaque(copy);
256 return SkImageEncoder::EncodeFile(path.c_str(), copy,
257 SkImageEncoder::kPNG_Type, 100);
258}
259
260// from gm
261static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
262 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
263 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
264 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
265
266 return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
267}
268
269/// Returns white on every pixel so that differences jump out at you;
270/// makes it easy to spot areas of difference that are in the least-significant
271/// bits.
272static inline SkPMColor compute_diff_white(SkPMColor c0, SkPMColor c1) {
273 return SkPackARGB32(0xFF, 0xFF, 0xFF, 0xFF);
274}
275
276static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1,
277 const int threshold) {
278 int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
279 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
280 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
281 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
282
283 return ((SkAbs32(da) <= threshold) &&
284 (SkAbs32(dr) <= threshold) &&
285 (SkAbs32(dg) <= threshold) &&
286 (SkAbs32(db) <= threshold));
287}
288
289// based on gm
290static void compute_diff(DiffRecord* dr,
291 DiffMetricProc diffFunction,
292 const int colorThreshold) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000293 SkAutoLockPixels alp(*dr->fDifferenceBitmap);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000294
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000295 const int w = dr->fComparisonBitmap->width();
296 const int h = dr->fComparisonBitmap->height();
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000297 int mismatchedPixels = 0;
298 int totalMismatchR = 0;
299 int totalMismatchG = 0;
300 int totalMismatchB = 0;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000301 // Accumulate fractionally different pixels, then divide out
302 // # of pixels at the end.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000303 dr->fWeightedFraction = 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000304 for (int y = 0; y < h; y++) {
305 for (int x = 0; x < w; x++) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000306 SkPMColor c0 = *dr->fBaseBitmap->getAddr32(x, y);
307 SkPMColor c1 = *dr->fComparisonBitmap->getAddr32(x, y);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000308 SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
309 SkPMColor outputDifference = diffFunction(c0, c1);
310 uint32_t thisR = SkGetPackedR32(trueDifference);
311 uint32_t thisG = SkGetPackedG32(trueDifference);
312 uint32_t thisB = SkGetPackedB32(trueDifference);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000313 totalMismatchR += thisR;
314 totalMismatchG += thisG;
315 totalMismatchB += thisB;
316 // In HSV, value is defined as max RGB component.
317 int value = MAX3(thisR, thisG, thisB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000318 dr->fWeightedFraction += ((float) value) / 255;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000319 if (thisR > dr->fMaxMismatchR) {
320 dr->fMaxMismatchR = thisR;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000321 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000322 if (thisG > dr->fMaxMismatchG) {
323 dr->fMaxMismatchG = thisG;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000324 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000325 if (thisB > dr->fMaxMismatchB) {
326 dr->fMaxMismatchB = thisB;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000327 }
328 if (!colors_match_thresholded(c0, c1, colorThreshold)) {
329 mismatchedPixels++;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000330 *dr->fDifferenceBitmap->getAddr32(x, y) = outputDifference;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000331 } else {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000332 *dr->fDifferenceBitmap->getAddr32(x, y) = 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000333 }
334 }
335 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000336 int pixelCount = w * h;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000337 dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
338 dr->fWeightedFraction /= pixelCount;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000339 dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
340 dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
341 dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000342}
343
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000344/// Given a image filename, returns the name of the file containing the
345/// associated difference image.
346static SkString filename_to_diff_filename (const SkString& filename) {
347 SkString diffName (filename);
348 const char* cstring = diffName.c_str();
349 int dotOffset = strrchr(cstring, '.') - cstring;
350 diffName.remove(dotOffset, diffName.size() - dotOffset);
351 diffName.append("-diff.png");
352 return diffName;
353}
354
tomhudson@google.com7d042802011-07-14 13:15:55 +0000355/// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo-actual.png"
356static SkString chrome_expected_path_to_actual (const SkString& expected) {
357 SkString actualPath (expected);
358 actualPath.remove(actualPath.size() - 13, 13);
359 actualPath.append("-actual.png");
360 return actualPath;
361}
362
363/// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo.png"
364static SkString chrome_expected_name_to_short (const SkString& expected) {
365 SkString shortName (expected);
366 shortName.remove(shortName.size() - 13, 13);
367 shortName.append(".png");
368 return shortName;
369}
370
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000371static void release_bitmaps(DiffRecord* drp) {
372 delete drp->fBaseBitmap;
373 drp->fBaseBitmap = NULL;
374 delete drp->fComparisonBitmap;
375 drp->fComparisonBitmap = NULL;
376 delete drp->fDifferenceBitmap;
377 drp->fDifferenceBitmap = NULL;
378}
379
tomhudson@google.com7d042802011-07-14 13:15:55 +0000380
381static void create_and_write_diff_image(DiffRecord* drp,
382 DiffMetricProc dmp,
383 const int colorThreshold,
384 const SkString& outputDir,
385 const SkString& filename) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000386 const int w = drp->fBaseWidth;
387 const int h = drp->fBaseHeight;
388 drp->fDifferenceBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
389 drp->fDifferenceBitmap->allocPixels();
tomhudson@google.com4e305982011-07-13 17:42:46 +0000390 compute_diff(drp, dmp, colorThreshold);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000391
392 SkString outPath (outputDir);
393 outPath.append(filename_to_diff_filename(filename));
394 write_bitmap(outPath, drp->fDifferenceBitmap);
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000395 release_bitmaps(drp);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000396}
397
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000398/// Creates difference images, returns the number that have a 0 metric.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000399static void create_diff_images (DiffMetricProc dmp,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000400 const int colorThreshold,
401 RecordArray* differences,
402 const SkString& baseDir,
403 const SkString& comparisonDir,
404 const SkString& outputDir,
405 DiffSummary* summary) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000406
407 //@todo thudson 19 Apr 2011
408 // this lets us know about files in baseDir not in compareDir, but it
409 // doesn't detect files in compareDir not in baseDir. Doing that
410 // efficiently seems to imply iterating through both directories to
411 // create a merged list, and then attempting to process every entry
412 // in that list?
413
414 SkOSFile::Iter baseIterator (baseDir.c_str());
415 SkString filename;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000416 while (baseIterator.next(&filename)) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000417 if (filename.endsWith(".pdf")) {
418 continue;
419 }
tomhudson@google.com4e305982011-07-13 17:42:46 +0000420 SkString basePath (baseDir);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000421 basePath.append(filename);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000422 SkString comparisonPath (comparisonDir);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000423 comparisonPath.append(filename);
424 DiffRecord * drp = new DiffRecord (filename, basePath, comparisonPath);
425 if (!get_bitmaps(drp)) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000426 continue;
427 }
428
tomhudson@google.com7d042802011-07-14 13:15:55 +0000429 create_and_write_diff_image(drp, dmp, colorThreshold,
430 outputDir, filename);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000431
432 differences->push(drp);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000433 summary->add(drp);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000434 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000435}
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000436
tomhudson@google.com7d042802011-07-14 13:15:55 +0000437static void create_diff_images_chromium (DiffMetricProc dmp,
438 const int colorThreshold,
439 RecordArray* differences,
440 const SkString& dirname,
441 const SkString& outputDir,
442 DiffSummary* summary) {
443 SkOSFile::Iter baseIterator (dirname.c_str());
444 SkString filename;
445 while (baseIterator.next(&filename)) {
446 if (filename.endsWith(".pdf")) {
447 continue;
448 }
449 if (filename.endsWith("-expected.png")) {
450 SkString expectedPath (dirname);
451 expectedPath.append(filename);
452 SkString shortName (chrome_expected_name_to_short(filename));
453 SkString actualPath (chrome_expected_path_to_actual(expectedPath));
454 DiffRecord * drp =
455 new DiffRecord (shortName, expectedPath, actualPath);
456 if (!get_bitmaps(drp)) {
457 continue;
458 }
459 create_and_write_diff_image(drp, dmp, colorThreshold,
460 outputDir, shortName);
461
462 differences->push(drp);
463 summary->add(drp);
464 }
465 }
466}
467
468static void analyze_chromium(DiffMetricProc dmp,
469 const int colorThreshold,
470 RecordArray* differences,
471 const SkString& dirname,
472 const SkString& outputDir,
473 DiffSummary* summary) {
474 create_diff_images_chromium(dmp, colorThreshold, differences,
475 dirname, outputDir, summary);
476 SkOSFile::Iter dirIterator(dirname.c_str());
477 SkString newdirname;
478 while (dirIterator.next(&newdirname, true)) {
479 if (newdirname.startsWith(".")) {
480 continue;
481 }
482 SkString fullname (dirname);
483 fullname.append(newdirname);
484 if (!fullname.endsWith("/")) {
485 fullname.append("/");
486 }
487 analyze_chromium(dmp, colorThreshold, differences,
488 fullname, outputDir, summary);
489 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000490}
491
492/// Make layout more consistent by scaling image to 240 height, 360 width,
493/// or natural size, whichever is smallest.
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000494static int compute_image_height (int height, int width) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000495 int retval = 240;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000496 if (height < retval) {
497 retval = height;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000498 }
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000499 float scale = (float) retval / height;
500 if (width * scale > 360) {
501 scale = (float) 360 / width;
502 retval = height * scale;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000503 }
504 return retval;
505}
506
507static void print_page_header (SkFILEWStream* stream,
508 const int matchCount,
509 const int colorThreshold,
510 const RecordArray& differences) {
511 SkTime::DateTime dt;
512 SkTime::GetDateTime(&dt);
513 stream->writeText("SkDiff run at ");
514 stream->writeDecAsText(dt.fHour);
515 stream->writeText(":");
516 if (dt.fMinute < 10) {
517 stream->writeText("0");
518 }
519 stream->writeDecAsText(dt.fMinute);
520 stream->writeText(":");
521 if (dt.fSecond < 10) {
522 stream->writeText("0");
523 }
524 stream->writeDecAsText(dt.fSecond);
525 stream->writeText("<br>");
526 stream->writeDecAsText(matchCount);
527 stream->writeText(" of ");
528 stream->writeDecAsText(differences.count());
529 stream->writeText(" images matched ");
530 if (colorThreshold == 0) {
531 stream->writeText("exactly");
532 } else {
533 stream->writeText("within ");
534 stream->writeDecAsText(colorThreshold);
535 stream->writeText(" color units per component");
536 }
537 stream->writeText(".<br>");
538
539}
540
541static void print_pixel_count (SkFILEWStream* stream,
542 const DiffRecord& diff) {
543 stream->writeText("<br>(");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000544 stream->writeDecAsText(diff.fFractionDifference *
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000545 diff.fBaseWidth *
546 diff.fBaseHeight);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000547 stream->writeText(" pixels)");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000548/*
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000549 stream->writeDecAsText(diff.fWeightedFraction *
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000550 diff.fBaseWidth *
551 diff.fBaseHeight);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000552 stream->writeText(" weighted pixels)");
553*/
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000554}
555
556static void print_label_cell (SkFILEWStream* stream,
557 const DiffRecord& diff) {
558 stream->writeText("<td>");
559 stream->writeText(diff.fFilename.c_str());
560 stream->writeText("<br>");
561 char metricBuf [20];
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000562 sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000563 stream->writeText(metricBuf);
564 stream->writeText(" of pixels differ");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000565 stream->writeText("\n (");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000566 sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000567 stream->writeText(metricBuf);
568 stream->writeText(" weighted)");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000569 // Write the actual number of pixels that differ if it's < 1%
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000570 if (diff.fFractionDifference < 0.01) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000571 print_pixel_count(stream, diff);
572 }
573 stream->writeText("<br>Average color mismatch ");
574 stream->writeDecAsText(MAX3(diff.fAverageMismatchR,
575 diff.fAverageMismatchG,
576 diff.fAverageMismatchB));
577 stream->writeText("<br>Max color mismatch ");
578 stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
579 diff.fMaxMismatchG,
580 diff.fMaxMismatchB));
581 stream->writeText("</td>");
582}
583
584static void print_image_cell (SkFILEWStream* stream,
tomhudson@google.com4e305982011-07-13 17:42:46 +0000585 const SkString& path,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000586 int height) {
587 stream->writeText("<td><a href=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000588 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000589 stream->writeText("\"><img src=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000590 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000591 stream->writeText("\" height=\"");
592 stream->writeDecAsText(height);
593 stream->writeText("px\"></a></td>");
594}
595
596static void print_diff_page (const int matchCount,
597 const int colorThreshold,
598 const RecordArray& differences,
599 const SkString& baseDir,
600 const SkString& comparisonDir,
601 const SkString& outputDir) {
602
tomhudson@google.com5b325292011-05-24 19:41:13 +0000603 SkString outputPath (outputDir);
604 outputPath.append("index.html");
605 //SkFILEWStream outputStream ("index.html");
606 SkFILEWStream outputStream (outputPath.c_str());
607
tomhudson@google.com4e305982011-07-13 17:42:46 +0000608 // Need to convert paths from relative-to-cwd to relative-to-outputDir
tomhudson@google.com5b325292011-05-24 19:41:13 +0000609 // FIXME this doesn't work if there are '..' inside the outputDir
610 unsigned int ui;
611 SkString relativePath;
612 for (ui = 0; ui < outputDir.size(); ui++) {
613 if (outputDir[ui] == '/') {
614 relativePath.append("../");
615 }
616 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000617
618 outputStream.writeText("<html>\n<body>\n");
619 print_page_header(&outputStream, matchCount, colorThreshold, differences);
620
621 outputStream.writeText("<table>\n");
622 int i;
623 for (i = 0; i < differences.count(); i++) {
624 DiffRecord* diff = differences[i];
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000625 if (0 == diff->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000626 continue;
627 }
tomhudson@google.com4e305982011-07-13 17:42:46 +0000628 if (!diff->fBasePath.startsWith("/")) {
629 diff->fBasePath.prepend(relativePath);
630 }
631 if (!diff->fComparisonPath.startsWith("/")) {
632 diff->fComparisonPath.prepend(relativePath);
633 }
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000634 int height = compute_image_height(diff->fBaseHeight, diff->fBaseWidth);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000635 outputStream.writeText("<tr>\n");
636 print_label_cell(&outputStream, *diff);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000637 print_image_cell(&outputStream, diff->fBasePath, height);
638 print_image_cell(&outputStream,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000639 filename_to_diff_filename(diff->fFilename), height);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000640 print_image_cell(&outputStream, diff->fComparisonPath, height);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000641 outputStream.writeText("</tr>\n");
642 outputStream.flush();
643 }
644 outputStream.writeText("</table>\n");
645 outputStream.writeText("</body>\n</html>\n");
646 outputStream.flush();
647}
648
649static void usage (char * argv0) {
650 SkDebugf("Skia baseline image diff tool\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000651 SkDebugf("Usage: %s baseDir comparisonDir [outputDir]\n", argv0);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000652 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000653" %s --chromium --release|--debug baseDir outputDir\n", argv0);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000654 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000655" --white: force all difference pixels to white\n"
656" --threshold n: only report differences > n (in one channel) [default 0]\n"
657" --sortbymismatch: sort by average color channel mismatch\n");
658 SkDebugf(
659" --sortbymaxmismatch: sort by worst color channel mismatch,\n"
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000660" break ties with -sortbymismatch,\n"
661" [default by fraction of pixels mismatching]\n");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000662 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000663" --weighted: sort by # pixels different weighted by color difference\n");
664 SkDebugf(
665" --chromium-release: process Webkit LayoutTests results instead of gm\n"
666" --chromium-debug: process Webkit LayoutTests results instead of gm\n");
667 SkDebugf(
668" baseDir: directory to read baseline images from,\n"
669" or chromium/src directory for --chromium.\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000670 SkDebugf(" comparisonDir: directory to read comparison images from\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000671 SkDebugf(
672" outputDir: directory to write difference images to; defaults to\n"
tomhudson@google.com7d042802011-07-14 13:15:55 +0000673" comparisonDir when not running --chromium\n");
674 SkDebugf("Also creates an \"index.html\" file in the output directory.\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000675}
676
677int main (int argc, char ** argv) {
678 DiffMetricProc diffProc = compute_diff_pmcolor;
679 SkQSortCompareProc sortProc = (SkQSortCompareProc) compare_diff_metrics;
680
681 // Maximum error tolerated in any one color channel in any one pixel before
682 // a difference is reported.
683 int colorThreshold = 0;
684 SkString baseDir;
685 SkString comparisonDir;
686 SkString outputDir;
687
tomhudson@google.com7d042802011-07-14 13:15:55 +0000688 bool analyzeChromium = false;
689 bool chromiumDebug = false;
690 bool chromiumRelease = false;
691
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000692 RecordArray differences;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000693 DiffSummary summary;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000694
695 int i, j;
696 for (i = 1, j = 0; i < argc; i++) {
tomhudson@google.com7d042802011-07-14 13:15:55 +0000697 if (!strcmp(argv[i], "--help")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000698 usage(argv[0]);
699 return 0;
700 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000701 if (!strcmp(argv[i], "--white")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000702 diffProc = compute_diff_white;
703 continue;
704 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000705 if (!strcmp(argv[i], "--sortbymismatch")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000706 sortProc = (SkQSortCompareProc) compare_diff_mean_mismatches;
707 continue;
708 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000709 if (!strcmp(argv[i], "--sortbymaxmismatch")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000710 sortProc = (SkQSortCompareProc) compare_diff_max_mismatches;
711 continue;
712 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000713 if (!strcmp(argv[i], "--weighted")) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000714 sortProc = (SkQSortCompareProc) compare_diff_weighted;
715 continue;
716 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000717 if (!strcmp(argv[i], "--chromium-release")) {
718 analyzeChromium = true;
719 chromiumRelease = true;
720 continue;
721 }
722 if (!strcmp(argv[i], "--chromium-debug")) {
723 analyzeChromium = true;
724 chromiumDebug = true;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000725 continue;
726 }
727 if (argv[i][0] != '-') {
728 switch (j++) {
729 case 0:
730 baseDir.set(argv[i]);
731 continue;
732 case 1:
733 comparisonDir.set(argv[i]);
734 continue;
735 case 2:
736 outputDir.set(argv[i]);
737 continue;
738 default:
739 usage(argv[0]);
740 return 0;
741 }
742 }
743
744 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
745 usage(argv[0]);
746 return 0;
747 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000748 if (analyzeChromium) {
749 if (j != 2) {
750 usage(argv[0]);
751 return 0;
752 }
753 if (chromiumRelease && chromiumDebug) {
754 SkDebugf(
755"--chromium must be either -release or -debug, not both!\n");
756 return 0;
757 }
758 }
759
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000760 if (j == 2) {
761 outputDir = comparisonDir;
762 } else if (j != 3) {
763 usage(argv[0]);
764 return 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000765 }
766
767 if (!baseDir.endsWith("/")) {
768 baseDir.append("/");
769 }
770 if (!comparisonDir.endsWith("/")) {
771 comparisonDir.append("/");
772 }
773 if (!outputDir.endsWith("/")) {
774 outputDir.append("/");
775 }
776
tomhudson@google.com7d042802011-07-14 13:15:55 +0000777 if (analyzeChromium) {
778 baseDir.append("webkit/");
779 if (chromiumRelease) {
780 baseDir.append("Release/");
781 }
782 if (chromiumDebug) {
783 baseDir.append("Debug/");
784 }
785 baseDir.append("layout-test-results/");
786 analyze_chromium(diffProc, colorThreshold, &differences,
787 baseDir, outputDir, &summary);
788 } else {
789 create_diff_images(diffProc, colorThreshold, &differences,
790 baseDir, comparisonDir, outputDir, &summary);
791 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000792 summary.print();
tomhudson@google.com7d042802011-07-14 13:15:55 +0000793
794 if (differences.count()) {
795 SkQSort(differences.begin(), differences.count(),
796 sizeof(DiffRecord*), sortProc);
797 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000798 print_diff_page(summary.fNumMatches, colorThreshold, differences,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000799 baseDir, comparisonDir, outputDir);
800
801 for (i = 0; i < differences.count(); i++) {
802 delete differences[i];
803 }
804}