blob: 5a057c4afffd0c13f4fa2b95694659db14999094 [file] [log] [blame]
tomhudson@google.com4b33d282011-04-27 15:39:30 +00001#include "SkColorPriv.h"
2#include "SkImageDecoder.h"
3#include "SkImageEncoder.h"
4#include "SkOSFile.h"
5#include "SkStream.h"
6#include "SkTDArray.h"
7#include "SkTemplates.h"
8#include "SkTime.h"
9#include "SkTSearch.h"
10#include "SkTypes.h"
11
12/**
13 * skdiff
14 *
15 * Given three directory names, expects to find identically-named files in
16 * each of the first two; the first are treated as a set of baseline,
17 * the second a set of variant images, and a diff image is written into the
18 * third directory for each pair.
19 * Creates an index.html in the current working directory to compare each
20 * pair that does not match exactly.
21 * Does *not* recursively descend directories.
22 */
23
24struct DiffRecord {
25 DiffRecord (const SkString filename)
26 : fFilename (filename)
tomhudson@google.com5b325292011-05-24 19:41:13 +000027 , fPercentDifference (0)
28 , fWeightedPercent (0)
tomhudson@google.com4b33d282011-04-27 15:39:30 +000029 , fAverageMismatchR (0)
30 , fAverageMismatchG (0)
31 , fAverageMismatchB (0)
32 , fMaxMismatchR (0)
33 , fMaxMismatchG (0)
34 , fMaxMismatchB (0) { };
35
36 SkString fFilename;
37
38 SkBitmap fBaseBitmap;
39 SkBitmap fComparisonBitmap;
40 SkBitmap fDifferenceBitmap;
41
42 /// Arbitrary floating-point metric to be used to sort images from most
43 /// to least different from baseline; values of 0 will be omitted from the
44 /// summary webpage.
tomhudson@google.com5b325292011-05-24 19:41:13 +000045 float fPercentDifference;
46 float fWeightedPercent;
tomhudson@google.com4b33d282011-04-27 15:39:30 +000047
48 float fAverageMismatchR;
49 float fAverageMismatchG;
50 float fAverageMismatchB;
51
52 uint32_t fMaxMismatchR;
53 uint32_t fMaxMismatchG;
54 uint32_t fMaxMismatchB;
55};
56
57typedef SkTDArray<DiffRecord*> RecordArray;
58
tomhudson@google.com5b325292011-05-24 19:41:13 +000059/// Comparison routine for qsort; sorts by fPercentDifference
tomhudson@google.com4b33d282011-04-27 15:39:30 +000060/// from largest to smallest.
61static int compare_diff_metrics (DiffRecord** lhs, DiffRecord** rhs) {
tomhudson@google.com5b325292011-05-24 19:41:13 +000062 if ((*lhs)->fPercentDifference < (*rhs)->fPercentDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +000063 return 1;
64 }
tomhudson@google.com5b325292011-05-24 19:41:13 +000065 if ((*rhs)->fPercentDifference < (*lhs)->fPercentDifference) {
66 return -1;
67 }
68 return 0;
69}
70
71static int compare_diff_weighted (DiffRecord** lhs, DiffRecord** rhs) {
72 if ((*lhs)->fWeightedPercent < (*rhs)->fWeightedPercent) {
73 return 1;
74 }
75 if ((*lhs)->fWeightedPercent > (*rhs)->fWeightedPercent) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +000076 return -1;
77 }
78 return 0;
79}
80
81#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
82#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
83/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
84/// from largest to smallest.
85static int compare_diff_mean_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
86 float leftValue = MAX3((*lhs)->fAverageMismatchR,
87 (*lhs)->fAverageMismatchG,
88 (*lhs)->fAverageMismatchB);
89 float rightValue = MAX3((*rhs)->fAverageMismatchR,
90 (*rhs)->fAverageMismatchG,
91 (*rhs)->fAverageMismatchB);
92 if (leftValue < rightValue) {
93 return 1;
94 }
95 if (rightValue < leftValue) {
96 return -1;
97 }
98 return 0;
99}
100
101/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB})
102/// from largest to smallest.
103static int compare_diff_max_mismatches (DiffRecord** lhs, DiffRecord** rhs) {
104 float leftValue = MAX3((*lhs)->fMaxMismatchR,
105 (*lhs)->fMaxMismatchG,
106 (*lhs)->fMaxMismatchB);
107 float rightValue = MAX3((*rhs)->fMaxMismatchR,
108 (*rhs)->fMaxMismatchG,
109 (*rhs)->fMaxMismatchB);
110 if (leftValue < rightValue) {
111 return 1;
112 }
113 if (rightValue < leftValue) {
114 return -1;
115 }
116 return compare_diff_mean_mismatches(lhs, rhs);
117}
118
119
120
121/// Parameterized routine to compute the color of a pixel in a difference image.
122typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
123
124static bool get_bitmaps (DiffRecord* diffRecord,
125 const SkString& baseDir,
126 const SkString& comparisonDir) {
127 SkString comparePath (comparisonDir);
128 comparePath.append(diffRecord->fFilename);
129 SkFILEStream compareStream(comparePath.c_str());
130 if (!compareStream.isValid()) {
131 SkDebugf("WARNING: couldn't open comparison file <%s>\n",
132 comparePath.c_str());
133 return false;
134 }
135
136 SkString basePath (baseDir);
137 basePath.append(diffRecord->fFilename);
138 SkFILEStream baseStream(basePath.c_str());
139 if (!baseStream.isValid()) {
140 SkDebugf("ERROR: couldn't open base file <%s>\n",
141 basePath.c_str());
142 return false;
143 }
144
145 SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
146 if (NULL == codec) {
147 SkDebugf("ERROR: no codec found for <%s>\n",
148 basePath.c_str());
149 return false;
150 }
151
152 SkAutoTDelete<SkImageDecoder> ad(codec);
153
154 baseStream.rewind();
155 if (!codec->decode(&baseStream, &diffRecord->fBaseBitmap,
156 SkBitmap::kARGB_8888_Config,
157 SkImageDecoder::kDecodePixels_Mode)) {
158 SkDebugf("ERROR: codec failed for <%s>\n",
159 basePath.c_str());
160 return false;
161 }
162
163 if (!codec->decode(&compareStream, &diffRecord->fComparisonBitmap,
164 SkBitmap::kARGB_8888_Config,
165 SkImageDecoder::kDecodePixels_Mode)) {
166 SkDebugf("ERROR: codec failed for <%s>\n",
167 comparePath.c_str());
168 return false;
169 }
170
171 return true;
172}
173
174// from gm - thanks to PNG, we need to force all pixels 100% opaque
175static void force_all_opaque(const SkBitmap& bitmap) {
176 SkAutoLockPixels lock(bitmap);
177 for (int y = 0; y < bitmap.height(); y++) {
178 for (int x = 0; x < bitmap.width(); x++) {
179 *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
180 }
181 }
182}
183
184// from gm
185static bool write_bitmap(const SkString& path, const SkBitmap& bitmap) {
186 SkBitmap copy;
187 bitmap.copyTo(&copy, SkBitmap::kARGB_8888_Config);
188 force_all_opaque(copy);
189 return SkImageEncoder::EncodeFile(path.c_str(), copy,
190 SkImageEncoder::kPNG_Type, 100);
191}
192
193// from gm
194static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
195 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
196 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
197 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
198
199 return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
200}
201
202/// Returns white on every pixel so that differences jump out at you;
203/// makes it easy to spot areas of difference that are in the least-significant
204/// bits.
205static inline SkPMColor compute_diff_white(SkPMColor c0, SkPMColor c1) {
206 return SkPackARGB32(0xFF, 0xFF, 0xFF, 0xFF);
207}
208
209static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1,
210 const int threshold) {
211 int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
212 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
213 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
214 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
215
216 return ((SkAbs32(da) <= threshold) &&
217 (SkAbs32(dr) <= threshold) &&
218 (SkAbs32(dg) <= threshold) &&
219 (SkAbs32(db) <= threshold));
220}
221
222// based on gm
223static void compute_diff(DiffRecord* dr,
224 DiffMetricProc diffFunction,
225 const int colorThreshold) {
226 SkAutoLockPixels alp(dr->fDifferenceBitmap);
227
228 const int w = dr->fComparisonBitmap.width();
229 const int h = dr->fComparisonBitmap.height();
230 int mismatchedPixels = 0;
231 int totalMismatchR = 0;
232 int totalMismatchG = 0;
233 int totalMismatchB = 0;
tomhudson@google.com5b325292011-05-24 19:41:13 +0000234 // Accumulate fractionally different pixels, then divide out
235 // # of pixels at the end.
236 dr->fWeightedPercent = 0;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000237 for (int y = 0; y < h; y++) {
238 for (int x = 0; x < w; x++) {
239 SkPMColor c0 = *dr->fBaseBitmap.getAddr32(x, y);
240 SkPMColor c1 = *dr->fComparisonBitmap.getAddr32(x, y);
241 SkPMColor d = 0;
242 d = diffFunction(c0, c1);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000243 uint32_t thisR = SkGetPackedR32(d);
244 uint32_t thisG = SkGetPackedG32(d);
245 uint32_t thisB = SkGetPackedB32(d);
246 totalMismatchR += thisR;
247 totalMismatchG += thisG;
248 totalMismatchB += thisB;
249 // In HSV, value is defined as max RGB component.
250 int value = MAX3(thisR, thisG, thisB);
251 dr->fWeightedPercent += ((float) value) / 255;
252 if (thisR > dr->fMaxMismatchR) {
253 dr->fMaxMismatchR = thisR;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000254 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000255 if (thisG > dr->fMaxMismatchG) {
256 dr->fMaxMismatchG = thisG;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000257 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000258 if (thisB > dr->fMaxMismatchB) {
259 dr->fMaxMismatchB = thisB;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000260 }
261 if (!colors_match_thresholded(c0, c1, colorThreshold)) {
262 mismatchedPixels++;
263 *dr->fDifferenceBitmap.getAddr32(x, y) = d;
264 } else {
265 *dr->fDifferenceBitmap.getAddr32(x, y) = 0;
266 }
267 }
268 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000269 int pixelCount = w * h;
270 dr->fPercentDifference = ((float) mismatchedPixels) / pixelCount;
271 dr->fWeightedPercent /= pixelCount;
272 dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
273 dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
274 dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000275}
276
277/// Creates difference images, returns the number that have a 0 metric.
278static int create_diff_images (DiffMetricProc dmp,
279 SkQSortCompareProc scp,
280 const int colorThreshold,
281 RecordArray* differences,
282 const SkString& baseDir,
283 const SkString& comparisonDir,
284 const SkString& outputDir) {
285
286 //@todo thudson 19 Apr 2011
287 // this lets us know about files in baseDir not in compareDir, but it
288 // doesn't detect files in compareDir not in baseDir. Doing that
289 // efficiently seems to imply iterating through both directories to
290 // create a merged list, and then attempting to process every entry
291 // in that list?
292
293 SkOSFile::Iter baseIterator (baseDir.c_str());
294 SkString filename;
295 int matchCount = 0;
296 while (baseIterator.next(&filename)) {
tomhudson@google.com5b325292011-05-24 19:41:13 +0000297 if (filename.endsWith(".pdf")) {
298 continue;
299 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000300 DiffRecord * drp = new DiffRecord (filename);
301 if (!get_bitmaps(drp, baseDir, comparisonDir)) {
302 continue;
303 }
304
305 const int w = drp->fBaseBitmap.width();
306 const int h = drp->fBaseBitmap.height();
307 drp->fDifferenceBitmap.setConfig(SkBitmap::kARGB_8888_Config, w, h);
308 drp->fDifferenceBitmap.allocPixels();
309 compute_diff(drp, dmp, colorThreshold);
310
311 SkString outPath (outputDir);
312 outPath.append(filename);
313 write_bitmap(outPath, drp->fDifferenceBitmap);
314
315 differences->push(drp);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000316 if (0 == drp->fPercentDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000317 matchCount++;
318 }
319 }
320
321 SkQSort(differences->begin(), differences->count(), sizeof(DiffRecord*),
322 scp);
323 return matchCount;
324}
325
326/// Make layout more consistent by scaling image to 240 height, 360 width,
327/// or natural size, whichever is smallest.
328static int compute_image_height (const SkBitmap& bmp) {
329 int retval = 240;
330 if (bmp.height() < retval) {
331 retval = bmp.height();
332 }
333 float scale = (float) retval / bmp.height();
334 if (bmp.width() * scale > 360) {
335 scale = (float) 360 / bmp.width();
336 retval = bmp.height() * scale;
337 }
338 return retval;
339}
340
341static void print_page_header (SkFILEWStream* stream,
342 const int matchCount,
343 const int colorThreshold,
344 const RecordArray& differences) {
345 SkTime::DateTime dt;
346 SkTime::GetDateTime(&dt);
347 stream->writeText("SkDiff run at ");
348 stream->writeDecAsText(dt.fHour);
349 stream->writeText(":");
350 if (dt.fMinute < 10) {
351 stream->writeText("0");
352 }
353 stream->writeDecAsText(dt.fMinute);
354 stream->writeText(":");
355 if (dt.fSecond < 10) {
356 stream->writeText("0");
357 }
358 stream->writeDecAsText(dt.fSecond);
359 stream->writeText("<br>");
360 stream->writeDecAsText(matchCount);
361 stream->writeText(" of ");
362 stream->writeDecAsText(differences.count());
363 stream->writeText(" images matched ");
364 if (colorThreshold == 0) {
365 stream->writeText("exactly");
366 } else {
367 stream->writeText("within ");
368 stream->writeDecAsText(colorThreshold);
369 stream->writeText(" color units per component");
370 }
371 stream->writeText(".<br>");
372
373}
374
375static void print_pixel_count (SkFILEWStream* stream,
376 const DiffRecord& diff) {
377 stream->writeText("<br>(");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000378 stream->writeDecAsText(diff.fPercentDifference *
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000379 diff.fBaseBitmap.width() *
380 diff.fBaseBitmap.height());
381 stream->writeText(" pixels)");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000382/*
383 stream->writeDecAsText(diff.fWeightedPercent *
384 diff.fBaseBitmap.width() *
385 diff.fBaseBitmap.height());
386 stream->writeText(" weighted pixels)");
387*/
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000388}
389
390static void print_label_cell (SkFILEWStream* stream,
391 const DiffRecord& diff) {
392 stream->writeText("<td>");
393 stream->writeText(diff.fFilename.c_str());
394 stream->writeText("<br>");
395 char metricBuf [20];
tomhudson@google.com5b325292011-05-24 19:41:13 +0000396 sprintf(metricBuf, "%12.4f%%", 100 * diff.fPercentDifference);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000397 stream->writeText(metricBuf);
398 stream->writeText(" of pixels differ");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000399 stream->writeText("\n (");
400 sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedPercent);
401 stream->writeText(metricBuf);
402 stream->writeText(" weighted)");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000403 // Write the actual number of pixels that differ if it's < 1%
tomhudson@google.com5b325292011-05-24 19:41:13 +0000404 if (diff.fPercentDifference < 0.01) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000405 print_pixel_count(stream, diff);
406 }
407 stream->writeText("<br>Average color mismatch ");
408 stream->writeDecAsText(MAX3(diff.fAverageMismatchR,
409 diff.fAverageMismatchG,
410 diff.fAverageMismatchB));
411 stream->writeText("<br>Max color mismatch ");
412 stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
413 diff.fMaxMismatchG,
414 diff.fMaxMismatchB));
415 stream->writeText("</td>");
416}
417
418static void print_image_cell (SkFILEWStream* stream,
419 const SkString& directory,
420 const SkString& filename,
421 int height) {
422 stream->writeText("<td><a href=\"");
423 stream->writeText(directory.c_str());
424 stream->writeText(filename.c_str());
425 stream->writeText("\"><img src=\"");
426 stream->writeText(directory.c_str());
427 stream->writeText(filename.c_str());
428 stream->writeText("\" height=\"");
429 stream->writeDecAsText(height);
430 stream->writeText("px\"></a></td>");
431}
432
433static void print_diff_page (const int matchCount,
434 const int colorThreshold,
435 const RecordArray& differences,
436 const SkString& baseDir,
437 const SkString& comparisonDir,
438 const SkString& outputDir) {
439
tomhudson@google.com5b325292011-05-24 19:41:13 +0000440 const SkString localDir ("");
441 SkString outputPath (outputDir);
442 outputPath.append("index.html");
443 //SkFILEWStream outputStream ("index.html");
444 SkFILEWStream outputStream (outputPath.c_str());
445
446 // FIXME this doesn't work if there are '..' inside the outputDir
447 unsigned int ui;
448 SkString relativePath;
449 for (ui = 0; ui < outputDir.size(); ui++) {
450 if (outputDir[ui] == '/') {
451 relativePath.append("../");
452 }
453 }
454 SkString relativeBaseDir (relativePath);
455 SkString relativeComparisonDir (relativePath);
456 relativeBaseDir.append(baseDir);
457 relativeComparisonDir.append(comparisonDir);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000458
459 outputStream.writeText("<html>\n<body>\n");
460 print_page_header(&outputStream, matchCount, colorThreshold, differences);
461
462 outputStream.writeText("<table>\n");
463 int i;
464 for (i = 0; i < differences.count(); i++) {
465 DiffRecord* diff = differences[i];
tomhudson@google.com5b325292011-05-24 19:41:13 +0000466 if (0 == diff->fPercentDifference) {
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000467 continue;
468 }
469 int height = compute_image_height(diff->fBaseBitmap);
470 outputStream.writeText("<tr>\n");
471 print_label_cell(&outputStream, *diff);
tomhudson@google.com5b325292011-05-24 19:41:13 +0000472 print_image_cell(&outputStream, relativeBaseDir,
473 diff->fFilename, height);
474 print_image_cell(&outputStream, localDir, diff->fFilename, height);
475 print_image_cell(&outputStream, relativeComparisonDir,
476 diff->fFilename, height);
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000477 outputStream.writeText("</tr>\n");
478 outputStream.flush();
479 }
480 outputStream.writeText("</table>\n");
481 outputStream.writeText("</body>\n</html>\n");
482 outputStream.flush();
483}
484
485static void usage (char * argv0) {
486 SkDebugf("Skia baseline image diff tool\n");
487 SkDebugf("Usage: %s baseDir comparisonDir outputDir\n", argv0);
488 SkDebugf(
489" -white: force all difference pixels to white\n"
490" -threshold n: only report differences > n (in one channel) [default 0]\n"
491" -sortbymismatch: sort by average color channel mismatch\n");
492 SkDebugf(
493" -sortbymaxmismatch: sort by worst color channel mismatch,\n"
494" break ties with -sortbymismatch,\n"
495" [default by fraction of pixels mismatching]\n");
tomhudson@google.com5b325292011-05-24 19:41:13 +0000496 SkDebugf(
497" -weighted: sort by # pixels different weighted by color difference\n");
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000498 SkDebugf(" baseDir: directory to read baseline images from\n");
499 SkDebugf(" comparisonDir: directory to read comparison images from\n");
500 SkDebugf(" outputDir: directory to write difference images to\n");
501 SkDebugf("Also creates an \"index.html\" file in the current directory.\n");
502}
503
504int main (int argc, char ** argv) {
505 DiffMetricProc diffProc = compute_diff_pmcolor;
506 SkQSortCompareProc sortProc = (SkQSortCompareProc) compare_diff_metrics;
507
508 // Maximum error tolerated in any one color channel in any one pixel before
509 // a difference is reported.
510 int colorThreshold = 0;
511 SkString baseDir;
512 SkString comparisonDir;
513 SkString outputDir;
514
515 RecordArray differences;
516
517 int i, j;
518 for (i = 1, j = 0; i < argc; i++) {
519 if (!strcmp(argv[i], "-help")) {
520 usage(argv[0]);
521 return 0;
522 }
523 if (!strcmp(argv[i], "-white")) {
524 diffProc = compute_diff_white;
525 continue;
526 }
527 if (!strcmp(argv[i], "-sortbymismatch")) {
528 sortProc = (SkQSortCompareProc) compare_diff_mean_mismatches;
529 continue;
530 }
531 if (!strcmp(argv[i], "-sortbymaxmismatch")) {
532 sortProc = (SkQSortCompareProc) compare_diff_max_mismatches;
533 continue;
534 }
tomhudson@google.com5b325292011-05-24 19:41:13 +0000535 if (!strcmp(argv[i], "-weighted")) {
536 sortProc = (SkQSortCompareProc) compare_diff_weighted;
537 continue;
538 }
tomhudson@google.com4b33d282011-04-27 15:39:30 +0000539 if (!strcmp(argv[i], "-threshold")) {
540 colorThreshold = atoi(argv[++i]);
541 continue;
542 }
543 if (argv[i][0] != '-') {
544 switch (j++) {
545 case 0:
546 baseDir.set(argv[i]);
547 continue;
548 case 1:
549 comparisonDir.set(argv[i]);
550 continue;
551 case 2:
552 outputDir.set(argv[i]);
553 continue;
554 default:
555 usage(argv[0]);
556 return 0;
557 }
558 }
559
560 SkDebugf("Unrecognized argument <%s>\n", argv[i]);
561 usage(argv[0]);
562 return 0;
563 }
564 if (j != 3) {
565 usage(argv[0]);
566 return 0;
567 }
568
569 if (!baseDir.endsWith("/")) {
570 baseDir.append("/");
571 }
572 if (!comparisonDir.endsWith("/")) {
573 comparisonDir.append("/");
574 }
575 if (!outputDir.endsWith("/")) {
576 outputDir.append("/");
577 }
578
579 int matchCount = create_diff_images(diffProc, sortProc,
580 colorThreshold, &differences,
581 baseDir, comparisonDir,
582 outputDir);
583 print_diff_page(matchCount, colorThreshold, differences,
584 baseDir, comparisonDir, outputDir);
585
586 for (i = 0; i < differences.count(); i++) {
587 delete differences[i];
588 }
589}