blob: 50136780d88a618d367e11cb8fcf2507031750b5 [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 ())
epoger@google.com25d961c2012-02-02 20:50:36 +000053 , fWhiteBitmap (new SkBitmap ())
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000054 , fBaseHeight (0)
55 , fBaseWidth (0)
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000056 , fFractionDifference (0)
57 , fWeightedFraction (0)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000058 , fAverageMismatchR (0)
59 , fAverageMismatchG (0)
60 , fAverageMismatchB (0)
61 , fMaxMismatchR (0)
62 , fMaxMismatchG (0)
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +000063 , fMaxMismatchB (0)
64 , fDoImageSizesMismatch (false) {
tomhudson@google.com7d042802011-07-14 13:15:55 +000065 // These asserts are valid for GM, but not for --chromium
66 //SkASSERT(basePath.endsWith(filename.c_str()));
67 //SkASSERT(comparisonPath.endsWith(filename.c_str()));
tomhudson@google.com4e305982011-07-13 17:42:46 +000068 };
tomhudson@google.com4b33d282011-04-27 15:39:30 +000069
70 SkString fFilename;
tomhudson@google.com4e305982011-07-13 17:42:46 +000071 SkString fBasePath;
72 SkString fComparisonPath;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000073
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000074 SkBitmap* fBaseBitmap;
75 SkBitmap* fComparisonBitmap;
76 SkBitmap* fDifferenceBitmap;
epoger@google.com25d961c2012-02-02 20:50:36 +000077 SkBitmap* fWhiteBitmap;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +000078
79 int fBaseHeight;
80 int fBaseWidth;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000081
82 /// Arbitrary floating-point metric to be used to sort images from most
83 /// to least different from baseline; values of 0 will be omitted from the
84 /// summary webpage.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +000085 float fFractionDifference;
86 float fWeightedFraction;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000087
88 float fAverageMismatchR;
89 float fAverageMismatchG;
90 float fAverageMismatchB;
91
92 uint32_t fMaxMismatchR;
93 uint32_t fMaxMismatchG;
94 uint32_t fMaxMismatchB;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +000095
96 /// By the time we need to report image size mismatch, we've already
97 /// released the bitmaps, so we need to remember it when we detect it.
98 bool fDoImageSizesMismatch;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000099};
100
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000101#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
102#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
103
epoger@google.com25d961c2012-02-02 20:50:36 +0000104const SkPMColor PMCOLOR_WHITE = SkPreMultiplyColor(SK_ColorWHITE);
105const SkPMColor PMCOLOR_BLACK = SkPreMultiplyColor(SK_ColorBLACK);
106
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000107struct DiffSummary {
108 DiffSummary ()
109 : fNumMatches (0)
110 , fNumMismatches (0)
111 , fMaxMismatchV (0)
112 , fMaxMismatchPercent (0) { };
113
114 uint32_t fNumMatches;
115 uint32_t fNumMismatches;
116 uint32_t fMaxMismatchV;
117 float fMaxMismatchPercent;
118
119 void print () {
120 printf("%d of %d images matched.\n", fNumMatches,
121 fNumMatches + fNumMismatches);
122 if (fNumMismatches > 0) {
123 printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV);
124 printf("Largest area mismatch was %.2f%% of pixels\n",
125 fMaxMismatchPercent);
126 }
127
128 }
129
130 void add (DiffRecord* drp) {
131 if (0 == drp->fFractionDifference) {
132 fNumMatches++;
133 } else {
134 fNumMismatches++;
135 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
136 fMaxMismatchPercent = drp->fFractionDifference * 100;
137 }
tomhudson@google.com88a0e052011-06-09 18:54:01 +0000138 uint32_t value = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG,
139 drp->fMaxMismatchB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000140 if (value > fMaxMismatchV) {
141 fMaxMismatchV = value;
142 }
143 }
144 }
145};
146
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000147typedef SkTDArray<DiffRecord*> RecordArray;
148
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000149/// Comparison routine for qsort; sorts by fFractionDifference
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000150/// from largest to smallest.
151static int compare_diff_metrics (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000152 if ((*lhs)->fFractionDifference < (*rhs)->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000153 return 1;
154 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000155 if ((*rhs)->fFractionDifference < (*lhs)->fFractionDifference) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000156 return -1;
157 }
158 return 0;
159}
160
161static int compare_diff_weighted (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000162 if ((*lhs)->fWeightedFraction < (*rhs)->fWeightedFraction) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000163 return 1;
164 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000165 if ((*lhs)->fWeightedFraction > (*rhs)->fWeightedFraction) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000166 return -1;
167 }
168 return 0;
169}
170
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000171/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
172/// from largest to smallest.
173static int compare_diff_mean_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
174 float leftValue = MAX3((*lhs)->fAverageMismatchR,
175 (*lhs)->fAverageMismatchG,
176 (*lhs)->fAverageMismatchB);
177 float rightValue = MAX3((*rhs)->fAverageMismatchR,
178 (*rhs)->fAverageMismatchG,
179 (*rhs)->fAverageMismatchB);
180 if (leftValue < rightValue) {
181 return 1;
182 }
183 if (rightValue < leftValue) {
184 return -1;
185 }
186 return 0;
187}
188
189/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB})
190/// from largest to smallest.
191static int compare_diff_max_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000192 uint32_t leftValue = MAX3((*lhs)->fMaxMismatchR,
193 (*lhs)->fMaxMismatchG,
194 (*lhs)->fMaxMismatchB);
195 uint32_t rightValue = MAX3((*rhs)->fMaxMismatchR,
196 (*rhs)->fMaxMismatchG,
197 (*rhs)->fMaxMismatchB);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000198 if (leftValue < rightValue) {
199 return 1;
200 }
201 if (rightValue < leftValue) {
202 return -1;
203 }
204 return compare_diff_mean_mismatches(lhs, rhs);
205}
206
207
208
209/// Parameterized routine to compute the color of a pixel in a difference image.
210typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
211
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000212static void expand_and_copy (int width, int height, SkBitmap** dest) {
213 SkBitmap* temp = new SkBitmap ();
214 temp->reset();
215 temp->setConfig((*dest)->config(), width, height);
216 temp->allocPixels();
217 (*dest)->copyPixelsTo(temp->getPixels(), temp->getSize(),
218 temp->rowBytes());
219 *dest = temp;
220}
221
tomhudson@google.com4e305982011-07-13 17:42:46 +0000222static bool get_bitmaps (DiffRecord* diffRecord) {
223 SkFILEStream compareStream(diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000224 if (!compareStream.isValid()) {
225 SkDebugf("WARNING: couldn't open comparison file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000226 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000227 return false;
228 }
229
tomhudson@google.com4e305982011-07-13 17:42:46 +0000230 SkFILEStream baseStream(diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000231 if (!baseStream.isValid()) {
232 SkDebugf("ERROR: couldn't open base file <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000233 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000234 return false;
235 }
236
237 SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
238 if (NULL == codec) {
239 SkDebugf("ERROR: no codec found for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000240 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000241 return false;
242 }
243
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000244 // In debug, the DLL will automatically be unloaded when this is deleted,
245 // but that shouldn't be a problem in release mode.
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000246 SkAutoTDelete<SkImageDecoder> ad(codec);
247
248 baseStream.rewind();
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000249 if (!codec->decode(&baseStream, diffRecord->fBaseBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000250 SkBitmap::kARGB_8888_Config,
251 SkImageDecoder::kDecodePixels_Mode)) {
252 SkDebugf("ERROR: codec failed for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000253 diffRecord->fBasePath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000254 return false;
255 }
256
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000257 diffRecord->fBaseWidth = diffRecord->fBaseBitmap->width();
258 diffRecord->fBaseHeight = diffRecord->fBaseBitmap->height();
259
260 if (!codec->decode(&compareStream, diffRecord->fComparisonBitmap,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000261 SkBitmap::kARGB_8888_Config,
262 SkImageDecoder::kDecodePixels_Mode)) {
263 SkDebugf("ERROR: codec failed for <%s>\n",
tomhudson@google.com4e305982011-07-13 17:42:46 +0000264 diffRecord->fComparisonPath.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000265 return false;
266 }
267
268 return true;
269}
270
271// from gm - thanks to PNG, we need to force all pixels 100% opaque
272static void force_all_opaque(const SkBitmap& bitmap) {
273 SkAutoLockPixels lock(bitmap);
274 for (int y = 0; y < bitmap.height(); y++) {
275 for (int x = 0; x < bitmap.width(); x++) {
276 *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
277 }
278 }
279}
280
281// from gm
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000282static bool write_bitmap(const SkString& path, const SkBitmap* bitmap) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000283 SkBitmap copy;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000284 bitmap->copyTo(&copy, SkBitmap::kARGB_8888_Config);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000285 force_all_opaque(copy);
286 return SkImageEncoder::EncodeFile(path.c_str(), copy,
287 SkImageEncoder::kPNG_Type, 100);
288}
289
290// from gm
291static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
292 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
293 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
294 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
295
296 return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
297}
298
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000299static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1,
300 const int threshold) {
301 int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
302 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
303 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
304 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
305
306 return ((SkAbs32(da) <= threshold) &&
307 (SkAbs32(dr) <= threshold) &&
308 (SkAbs32(dg) <= threshold) &&
309 (SkAbs32(db) <= threshold));
310}
311
312// based on gm
313static void compute_diff(DiffRecord* dr,
314 DiffMetricProc diffFunction,
315 const int colorThreshold) {
epoger@google.com25d961c2012-02-02 20:50:36 +0000316 SkAutoLockPixels alpDiff(*dr->fDifferenceBitmap);
317 SkAutoLockPixels alpWhite(*dr->fWhiteBitmap);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000318
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000319 const int w = dr->fComparisonBitmap->width();
320 const int h = dr->fComparisonBitmap->height();
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000321 int mismatchedPixels = 0;
322 int totalMismatchR = 0;
323 int totalMismatchG = 0;
324 int totalMismatchB = 0;
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000325
326 if (w != dr->fBaseWidth || h != dr->fBaseHeight) {
327 dr->fDoImageSizesMismatch = true;
328 dr->fFractionDifference = 1;
329 return;
330 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000331 // Accumulate fractionally different pixels, then divide out
332 // # of pixels at the end.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000333 dr->fWeightedFraction = 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000334 for (int y = 0; y < h; y++) {
335 for (int x = 0; x < w; x++) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000336 SkPMColor c0 = *dr->fBaseBitmap->getAddr32(x, y);
337 SkPMColor c1 = *dr->fComparisonBitmap->getAddr32(x, y);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000338 SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
339 SkPMColor outputDifference = diffFunction(c0, c1);
340 uint32_t thisR = SkGetPackedR32(trueDifference);
341 uint32_t thisG = SkGetPackedG32(trueDifference);
342 uint32_t thisB = SkGetPackedB32(trueDifference);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000343 totalMismatchR += thisR;
344 totalMismatchG += thisG;
345 totalMismatchB += thisB;
346 // In HSV, value is defined as max RGB component.
347 int value = MAX3(thisR, thisG, thisB);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000348 dr->fWeightedFraction += ((float) value) / 255;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000349 if (thisR > dr->fMaxMismatchR) {
350 dr->fMaxMismatchR = thisR;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000351 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000352 if (thisG > dr->fMaxMismatchG) {
353 dr->fMaxMismatchG = thisG;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000354 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000355 if (thisB > dr->fMaxMismatchB) {
356 dr->fMaxMismatchB = thisB;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000357 }
358 if (!colors_match_thresholded(c0, c1, colorThreshold)) {
359 mismatchedPixels++;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000360 *dr->fDifferenceBitmap->getAddr32(x, y) = outputDifference;
epoger@google.com25d961c2012-02-02 20:50:36 +0000361 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_WHITE;
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;
epoger@google.com25d961c2012-02-02 20:50:36 +0000364 *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_BLACK;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000365 }
366 }
367 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000368 int pixelCount = w * h;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000369 dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
370 dr->fWeightedFraction /= pixelCount;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000371 dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
372 dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
373 dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000374}
375
epoger@google.com25d961c2012-02-02 20:50:36 +0000376static SkString filename_to_derived_filename (const SkString& filename,
377 const char *suffix) {
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000378 SkString diffName (filename);
379 const char* cstring = diffName.c_str();
380 int dotOffset = strrchr(cstring, '.') - cstring;
381 diffName.remove(dotOffset, diffName.size() - dotOffset);
epoger@google.com25d961c2012-02-02 20:50:36 +0000382 diffName.append(suffix);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000383 return diffName;
384}
385
epoger@google.com25d961c2012-02-02 20:50:36 +0000386/// Given a image filename, returns the name of the file containing the
387/// associated difference image.
388static SkString filename_to_diff_filename (const SkString& filename) {
389 return filename_to_derived_filename(filename, "-diff.png");
390}
391
392/// Given a image filename, returns the name of the file containing the
393/// "white" difference image.
394static SkString filename_to_white_filename (const SkString& filename) {
395 return filename_to_derived_filename(filename, "-white.png");
396}
397
tomhudson@google.com7d042802011-07-14 13:15:55 +0000398/// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo-actual.png"
399static SkString chrome_expected_path_to_actual (const SkString& expected) {
400 SkString actualPath (expected);
401 actualPath.remove(actualPath.size() - 13, 13);
402 actualPath.append("-actual.png");
403 return actualPath;
404}
405
406/// Convert a chromium/WebKit LayoutTest "foo-expected.png" to "foo.png"
407static SkString chrome_expected_name_to_short (const SkString& expected) {
408 SkString shortName (expected);
409 shortName.remove(shortName.size() - 13, 13);
410 shortName.append(".png");
411 return shortName;
412}
413
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000414static void release_bitmaps(DiffRecord* drp) {
415 delete drp->fBaseBitmap;
416 drp->fBaseBitmap = NULL;
417 delete drp->fComparisonBitmap;
418 drp->fComparisonBitmap = NULL;
419 delete drp->fDifferenceBitmap;
420 drp->fDifferenceBitmap = NULL;
epoger@google.com25d961c2012-02-02 20:50:36 +0000421 delete drp->fWhiteBitmap;
422 drp->fWhiteBitmap = NULL;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000423}
424
tomhudson@google.com7d042802011-07-14 13:15:55 +0000425
426static void create_and_write_diff_image(DiffRecord* drp,
427 DiffMetricProc dmp,
428 const int colorThreshold,
429 const SkString& outputDir,
430 const SkString& filename) {
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000431 const int w = drp->fBaseWidth;
432 const int h = drp->fBaseHeight;
433 drp->fDifferenceBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
434 drp->fDifferenceBitmap->allocPixels();
epoger@google.com25d961c2012-02-02 20:50:36 +0000435 drp->fWhiteBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
436 drp->fWhiteBitmap->allocPixels();
tomhudson@google.com4e305982011-07-13 17:42:46 +0000437 compute_diff(drp, dmp, colorThreshold);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000438
epoger@google.com25d961c2012-02-02 20:50:36 +0000439 SkString differencePath (outputDir);
440 differencePath.append(filename_to_diff_filename(filename));
441 write_bitmap(differencePath, drp->fDifferenceBitmap);
442 SkString whitePath (outputDir);
443 whitePath.append(filename_to_white_filename(filename));
444 write_bitmap(whitePath, drp->fWhiteBitmap);
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000445 release_bitmaps(drp);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000446}
447
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000448/// Creates difference images, returns the number that have a 0 metric.
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000449static void create_diff_images (DiffMetricProc dmp,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000450 const int colorThreshold,
451 RecordArray* differences,
452 const SkString& baseDir,
453 const SkString& comparisonDir,
454 const SkString& outputDir,
455 DiffSummary* summary) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000456
457 //@todo thudson 19 Apr 2011
458 // this lets us know about files in baseDir not in compareDir, but it
459 // doesn't detect files in compareDir not in baseDir. Doing that
460 // efficiently seems to imply iterating through both directories to
461 // create a merged list, and then attempting to process every entry
462 // in that list?
463
464 SkOSFile::Iter baseIterator (baseDir.c_str());
465 SkString filename;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000466 while (baseIterator.next(&filename)) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000467 if (filename.endsWith(".pdf")) {
468 continue;
469 }
tomhudson@google.com4e305982011-07-13 17:42:46 +0000470 SkString basePath (baseDir);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000471 basePath.append(filename);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000472 SkString comparisonPath (comparisonDir);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000473 comparisonPath.append(filename);
474 DiffRecord * drp = new DiffRecord (filename, basePath, comparisonPath);
475 if (!get_bitmaps(drp)) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000476 continue;
477 }
478
tomhudson@google.com7d042802011-07-14 13:15:55 +0000479 create_and_write_diff_image(drp, dmp, colorThreshold,
480 outputDir, filename);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000481
482 differences->push(drp);
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000483 summary->add(drp);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000484 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000485}
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000486
tomhudson@google.com7d042802011-07-14 13:15:55 +0000487static void create_diff_images_chromium (DiffMetricProc dmp,
488 const int colorThreshold,
489 RecordArray* differences,
490 const SkString& dirname,
491 const SkString& outputDir,
492 DiffSummary* summary) {
493 SkOSFile::Iter baseIterator (dirname.c_str());
494 SkString filename;
495 while (baseIterator.next(&filename)) {
496 if (filename.endsWith(".pdf")) {
497 continue;
498 }
499 if (filename.endsWith("-expected.png")) {
500 SkString expectedPath (dirname);
501 expectedPath.append(filename);
502 SkString shortName (chrome_expected_name_to_short(filename));
503 SkString actualPath (chrome_expected_path_to_actual(expectedPath));
504 DiffRecord * drp =
505 new DiffRecord (shortName, expectedPath, actualPath);
506 if (!get_bitmaps(drp)) {
507 continue;
508 }
509 create_and_write_diff_image(drp, dmp, colorThreshold,
510 outputDir, shortName);
511
512 differences->push(drp);
513 summary->add(drp);
514 }
515 }
516}
517
518static void analyze_chromium(DiffMetricProc dmp,
519 const int colorThreshold,
520 RecordArray* differences,
521 const SkString& dirname,
522 const SkString& outputDir,
523 DiffSummary* summary) {
524 create_diff_images_chromium(dmp, colorThreshold, differences,
525 dirname, outputDir, summary);
526 SkOSFile::Iter dirIterator(dirname.c_str());
527 SkString newdirname;
528 while (dirIterator.next(&newdirname, true)) {
529 if (newdirname.startsWith(".")) {
530 continue;
531 }
532 SkString fullname (dirname);
533 fullname.append(newdirname);
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000534 if (!fullname.endsWith(PATH_DIV_STR)) {
535 fullname.append(PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000536 }
537 analyze_chromium(dmp, colorThreshold, differences,
538 fullname, outputDir, summary);
539 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000540}
541
542/// Make layout more consistent by scaling image to 240 height, 360 width,
543/// or natural size, whichever is smallest.
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000544static int compute_image_height (int height, int width) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000545 int retval = 240;
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000546 if (height < retval) {
547 retval = height;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000548 }
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000549 float scale = (float) retval / height;
550 if (width * scale > 360) {
551 scale = (float) 360 / width;
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000552 retval = static_cast<int>(height * scale);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000553 }
554 return retval;
555}
556
epoger@google.com25d961c2012-02-02 20:50:36 +0000557static void print_table_header (SkFILEWStream* stream,
558 const int matchCount,
559 const int colorThreshold,
560 const RecordArray& differences,
561 const SkString &baseDir,
562 const SkString &comparisonDir) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000563 SkTime::DateTime dt;
564 SkTime::GetDateTime(&dt);
epoger@google.com25d961c2012-02-02 20:50:36 +0000565 stream->writeText("<table>\n");
566 stream->writeText("<tr><th>");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000567 stream->writeText("SkDiff run at ");
568 stream->writeDecAsText(dt.fHour);
569 stream->writeText(":");
570 if (dt.fMinute < 10) {
571 stream->writeText("0");
572 }
573 stream->writeDecAsText(dt.fMinute);
574 stream->writeText(":");
575 if (dt.fSecond < 10) {
576 stream->writeText("0");
577 }
578 stream->writeDecAsText(dt.fSecond);
579 stream->writeText("<br>");
580 stream->writeDecAsText(matchCount);
581 stream->writeText(" of ");
582 stream->writeDecAsText(differences.count());
583 stream->writeText(" images matched ");
584 if (colorThreshold == 0) {
585 stream->writeText("exactly");
586 } else {
587 stream->writeText("within ");
588 stream->writeDecAsText(colorThreshold);
589 stream->writeText(" color units per component");
590 }
591 stream->writeText(".<br>");
epoger@google.com25d961c2012-02-02 20:50:36 +0000592 stream->writeText("</th>\n<th>");
593 stream->writeText("every different pixel shown in white");
594 stream->writeText("</th>\n<th>");
595 stream->writeText("color difference at each pixel");
596 stream->writeText("</th>\n<th>");
597 stream->writeText(baseDir.c_str());
598 stream->writeText("</th>\n<th>");
599 stream->writeText(comparisonDir.c_str());
600 stream->writeText("</th>\n");
601 stream->writeText("</tr>\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000602}
603
604static void print_pixel_count (SkFILEWStream* stream,
605 const DiffRecord& diff) {
606 stream->writeText("<br>(");
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000607 stream->writeDecAsText(static_cast<int>(diff.fFractionDifference *
608 diff.fBaseWidth *
609 diff.fBaseHeight));
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000610 stream->writeText(" pixels)");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000611/*
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000612 stream->writeDecAsText(diff.fWeightedFraction *
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000613 diff.fBaseWidth *
614 diff.fBaseHeight);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000615 stream->writeText(" weighted pixels)");
616*/
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000617}
618
619static void print_label_cell (SkFILEWStream* stream,
620 const DiffRecord& diff) {
621 stream->writeText("<td>");
622 stream->writeText(diff.fFilename.c_str());
623 stream->writeText("<br>");
tomhudson@google.com8b08c3e2011-11-30 17:01:00 +0000624 if (diff.fDoImageSizesMismatch) {
625 stream->writeText("Image sizes differ");
626 stream->writeText("</td>");
627 return;
628 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000629 char metricBuf [20];
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000630 sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000631 stream->writeText(metricBuf);
632 stream->writeText(" of pixels differ");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000633 stream->writeText("\n (");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000634 sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000635 stream->writeText(metricBuf);
636 stream->writeText(" weighted)");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000637 // Write the actual number of pixels that differ if it's < 1%
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000638 if (diff.fFractionDifference < 0.01) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000639 print_pixel_count(stream, diff);
640 }
641 stream->writeText("<br>Average color mismatch ");
bsalomon@google.com8e06dab2011-10-07 20:03:39 +0000642 stream->writeDecAsText(static_cast<int>(MAX3(diff.fAverageMismatchR,
643 diff.fAverageMismatchG,
644 diff.fAverageMismatchB)));
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000645 stream->writeText("<br>Max color mismatch ");
646 stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
647 diff.fMaxMismatchG,
648 diff.fMaxMismatchB));
649 stream->writeText("</td>");
650}
651
652static void print_image_cell (SkFILEWStream* stream,
tomhudson@google.com4e305982011-07-13 17:42:46 +0000653 const SkString& path,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000654 int height) {
655 stream->writeText("<td><a href=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000656 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000657 stream->writeText("\"><img src=\"");
tomhudson@google.com4e305982011-07-13 17:42:46 +0000658 stream->writeText(path.c_str());
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000659 stream->writeText("\" height=\"");
660 stream->writeDecAsText(height);
661 stream->writeText("px\"></a></td>");
662}
663
664static void print_diff_page (const int matchCount,
665 const int colorThreshold,
666 const RecordArray& differences,
667 const SkString& baseDir,
668 const SkString& comparisonDir,
669 const SkString& outputDir) {
670
tomhudson@google.com5b325292011-05-24 19:41:13 +0000671 SkString outputPath (outputDir);
672 outputPath.append("index.html");
673 //SkFILEWStream outputStream ("index.html");
674 SkFILEWStream outputStream (outputPath.c_str());
675
tomhudson@google.com4e305982011-07-13 17:42:46 +0000676 // Need to convert paths from relative-to-cwd to relative-to-outputDir
tomhudson@google.com5b325292011-05-24 19:41:13 +0000677 // FIXME this doesn't work if there are '..' inside the outputDir
678 unsigned int ui;
679 SkString relativePath;
680 for (ui = 0; ui < outputDir.size(); ui++) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000681 if (outputDir[ui] == PATH_DIV_CHAR) {
682 relativePath.append(".." PATH_DIV_STR);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000683 }
684 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000685
686 outputStream.writeText("<html>\n<body>\n");
epoger@google.com25d961c2012-02-02 20:50:36 +0000687 print_table_header(&outputStream, matchCount, colorThreshold, differences,
688 baseDir, comparisonDir);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000689 int i;
690 for (i = 0; i < differences.count(); i++) {
691 DiffRecord* diff = differences[i];
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000692 if (0 == diff->fFractionDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000693 continue;
694 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000695 if (!diff->fBasePath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000696 diff->fBasePath.prepend(relativePath);
697 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000698 if (!diff->fComparisonPath.startsWith(PATH_DIV_STR)) {
tomhudson@google.com4e305982011-07-13 17:42:46 +0000699 diff->fComparisonPath.prepend(relativePath);
700 }
tomhudson@google.com9b540ce2011-08-02 14:10:04 +0000701 int height = compute_image_height(diff->fBaseHeight, diff->fBaseWidth);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000702 outputStream.writeText("<tr>\n");
703 print_label_cell(&outputStream, *diff);
epoger@google.com25d961c2012-02-02 20:50:36 +0000704 print_image_cell(&outputStream,
705 filename_to_white_filename(diff->fFilename), height);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000706 print_image_cell(&outputStream,
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000707 filename_to_diff_filename(diff->fFilename), height);
epoger@google.com25d961c2012-02-02 20:50:36 +0000708 print_image_cell(&outputStream, diff->fBasePath, height);
tomhudson@google.com4e305982011-07-13 17:42:46 +0000709 print_image_cell(&outputStream, diff->fComparisonPath, height);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000710 outputStream.writeText("</tr>\n");
711 outputStream.flush();
712 }
713 outputStream.writeText("</table>\n");
714 outputStream.writeText("</body>\n</html>\n");
715 outputStream.flush();
716}
717
718static void usage (char * argv0) {
719 SkDebugf("Skia baseline image diff tool\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000720 SkDebugf("Usage: %s baseDir comparisonDir [outputDir]\n", argv0);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000721 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000722" %s --chromium --release|--debug baseDir outputDir\n", argv0);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000723 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000724" --threshold n: only report differences > n (in one channel) [default 0]\n"
725" --sortbymismatch: sort by average color channel mismatch\n");
726 SkDebugf(
727" --sortbymaxmismatch: sort by worst color channel mismatch,\n"
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000728" break ties with -sortbymismatch,\n"
729" [default by fraction of pixels mismatching]\n");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000730 SkDebugf(
tomhudson@google.com7d042802011-07-14 13:15:55 +0000731" --weighted: sort by # pixels different weighted by color difference\n");
732 SkDebugf(
733" --chromium-release: process Webkit LayoutTests results instead of gm\n"
734" --chromium-debug: process Webkit LayoutTests results instead of gm\n");
735 SkDebugf(
736" baseDir: directory to read baseline images from,\n"
737" or chromium/src directory for --chromium.\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000738 SkDebugf(" comparisonDir: directory to read comparison images from\n");
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000739 SkDebugf(
740" outputDir: directory to write difference images to; defaults to\n"
tomhudson@google.com7d042802011-07-14 13:15:55 +0000741" comparisonDir when not running --chromium\n");
742 SkDebugf("Also creates an \"index.html\" file in the output directory.\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000743}
744
745int main (int argc, char ** argv) {
746 DiffMetricProc diffProc = compute_diff_pmcolor;
747 SkQSortCompareProc sortProc = (SkQSortCompareProc) compare_diff_metrics;
748
749 // Maximum error tolerated in any one color channel in any one pixel before
750 // a difference is reported.
751 int colorThreshold = 0;
752 SkString baseDir;
753 SkString comparisonDir;
754 SkString outputDir;
755
tomhudson@google.com7d042802011-07-14 13:15:55 +0000756 bool analyzeChromium = false;
757 bool chromiumDebug = false;
758 bool chromiumRelease = false;
759
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000760 RecordArray differences;
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000761 DiffSummary summary;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000762
763 int i, j;
764 for (i = 1, j = 0; i < argc; i++) {
tomhudson@google.com7d042802011-07-14 13:15:55 +0000765 if (!strcmp(argv[i], "--help")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000766 usage(argv[0]);
767 return 0;
768 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000769 if (!strcmp(argv[i], "--sortbymismatch")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000770 sortProc = (SkQSortCompareProc) compare_diff_mean_mismatches;
771 continue;
772 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000773 if (!strcmp(argv[i], "--sortbymaxmismatch")) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000774 sortProc = (SkQSortCompareProc) compare_diff_max_mismatches;
775 continue;
776 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000777 if (!strcmp(argv[i], "--weighted")) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000778 sortProc = (SkQSortCompareProc) compare_diff_weighted;
779 continue;
780 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000781 if (!strcmp(argv[i], "--chromium-release")) {
782 analyzeChromium = true;
783 chromiumRelease = true;
784 continue;
785 }
786 if (!strcmp(argv[i], "--chromium-debug")) {
787 analyzeChromium = true;
788 chromiumDebug = true;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000789 continue;
790 }
791 if (argv[i][0] != '-') {
792 switch (j++) {
793 case 0:
794 baseDir.set(argv[i]);
795 continue;
796 case 1:
797 comparisonDir.set(argv[i]);
798 continue;
799 case 2:
800 outputDir.set(argv[i]);
801 continue;
802 default:
803 usage(argv[0]);
804 return 0;
805 }
806 }
807
808 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
809 usage(argv[0]);
810 return 0;
811 }
tomhudson@google.com7d042802011-07-14 13:15:55 +0000812 if (analyzeChromium) {
813 if (j != 2) {
814 usage(argv[0]);
815 return 0;
816 }
817 if (chromiumRelease && chromiumDebug) {
818 SkDebugf(
819"--chromium must be either -release or -debug, not both!\n");
820 return 0;
821 }
822 }
823
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000824 if (j == 2) {
825 outputDir = comparisonDir;
826 } else if (j != 3) {
827 usage(argv[0]);
828 return 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000829 }
830
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000831 if (!baseDir.endsWith(PATH_DIV_STR)) {
832 baseDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000833 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000834 if (!comparisonDir.endsWith(PATH_DIV_STR)) {
835 comparisonDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000836 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000837 if (!outputDir.endsWith(PATH_DIV_STR)) {
838 outputDir.append(PATH_DIV_STR);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000839 }
840
tomhudson@google.com7d042802011-07-14 13:15:55 +0000841 if (analyzeChromium) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000842 baseDir.append("webkit" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000843 if (chromiumRelease) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000844 baseDir.append("Release" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000845 }
846 if (chromiumDebug) {
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000847 baseDir.append("Debug" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000848 }
bsalomon@google.com1a315fe2011-09-23 14:56:37 +0000849 baseDir.append("layout-test-results" PATH_DIV_STR);
tomhudson@google.com7d042802011-07-14 13:15:55 +0000850 analyze_chromium(diffProc, colorThreshold, &differences,
851 baseDir, outputDir, &summary);
852 } else {
853 create_diff_images(diffProc, colorThreshold, &differences,
854 baseDir, comparisonDir, outputDir, &summary);
855 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000856 summary.print();
tomhudson@google.com7d042802011-07-14 13:15:55 +0000857
858 if (differences.count()) {
859 SkQSort(differences.begin(), differences.count(),
860 sizeof(DiffRecord*), sortProc);
861 }
tomhudson@google.com9dc527b2011-06-09 15:47:10 +0000862 print_diff_page(summary.fNumMatches, colorThreshold, differences,
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000863 baseDir, comparisonDir, outputDir);
864
865 for (i = 0; i < differences.count(); i++) {
866 delete differences[i];
867 }
868}