1 /* 2 * Copyright 2011 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 #include "skdiff.h" 8 #include "skdiff_html.h" 9 #include "skdiff_utils.h" 10 #include "SkBitmap.h" 11 #include "SkData.h" 12 #include "SkForceLinking.h" 13 #include "SkImageDecoder.h" 14 #include "SkImageEncoder.h" 15 #include "SkOSFile.h" 16 #include "SkStream.h" 17 #include "SkTDArray.h" 18 #include "SkTemplates.h" 19 #include "SkTSearch.h" 20 #include "SkTypes.h" 21 22 __SK_FORCE_IMAGE_DECODER_LINKING; 23 24 /** 25 * skdiff 26 * 27 * Given three directory names, expects to find identically-named files in 28 * each of the first two; the first are treated as a set of baseline, 29 * the second a set of variant images, and a diff image is written into the 30 * third directory for each pair. 31 * Creates an index.html in the current third directory to compare each 32 * pair that does not match exactly. 33 * Recursively descends directories, unless run with --norecurse. 34 * 35 * Returns zero exit code if all images match across baseDir and comparisonDir. 36 */ 37 38 typedef SkTDArray<SkString*> StringArray; 39 typedef StringArray FileArray; 40 41 struct DiffSummary { 42 DiffSummary () 43 : fNumMatches(0) 44 , fNumMismatches(0) 45 , fMaxMismatchV(0) 46 , fMaxMismatchPercent(0) { }; 47 48 ~DiffSummary() { 49 for (int i = 0; i < DiffRecord::kResultCount; ++i) { 50 fResultsOfType[i].deleteAll(); 51 } 52 for (int base = 0; base < DiffResource::kStatusCount; ++base) { 53 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { 54 fStatusOfType[base][comparison].deleteAll(); 55 } 56 } 57 } 58 59 uint32_t fNumMatches; 60 uint32_t fNumMismatches; 61 uint32_t fMaxMismatchV; 62 float fMaxMismatchPercent; 63 64 FileArray fResultsOfType[DiffRecord::kResultCount]; 65 FileArray fStatusOfType[DiffResource::kStatusCount][DiffResource::kStatusCount]; 66 67 void printContents(const FileArray& fileArray, 68 const char* baseStatus, const char* comparisonStatus, 69 bool listFilenames) { 70 int n = fileArray.count(); 71 printf("%d file pairs %s in baseDir and %s in comparisonDir", 72 n, baseStatus, comparisonStatus); 73 if (listFilenames) { 74 printf(": "); 75 for (int i = 0; i < n; ++i) { 76 printf("%s ", fileArray[i]->c_str()); 77 } 78 } 79 printf("\n"); 80 } 81 82 void printStatus(bool listFilenames, 83 bool failOnStatusType[DiffResource::kStatusCount] 84 [DiffResource::kStatusCount]) { 85 typedef DiffResource::Status Status; 86 87 for (int base = 0; base < DiffResource::kStatusCount; ++base) { 88 Status baseStatus = static_cast<Status>(base); 89 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { 90 Status comparisonStatus = static_cast<Status>(comparison); 91 const FileArray& fileArray = fStatusOfType[base][comparison]; 92 if (fileArray.count() > 0) { 93 if (failOnStatusType[base][comparison]) { 94 printf(" [*] "); 95 } else { 96 printf(" [_] "); 97 } 98 printContents(fileArray, 99 DiffResource::getStatusDescription(baseStatus), 100 DiffResource::getStatusDescription(comparisonStatus), 101 listFilenames); 102 } 103 } 104 } 105 } 106 107 // Print a line about the contents of this FileArray to stdout. 108 void printContents(const FileArray& fileArray, const char* headerText, bool listFilenames) { 109 int n = fileArray.count(); 110 printf("%d file pairs %s", n, headerText); 111 if (listFilenames) { 112 printf(": "); 113 for (int i = 0; i < n; ++i) { 114 printf("%s ", fileArray[i]->c_str()); 115 } 116 } 117 printf("\n"); 118 } 119 120 void print(bool listFilenames, bool failOnResultType[DiffRecord::kResultCount], 121 bool failOnStatusType[DiffResource::kStatusCount] 122 [DiffResource::kStatusCount]) { 123 printf("\ncompared %d file pairs:\n", fNumMatches + fNumMismatches); 124 for (int resultInt = 0; resultInt < DiffRecord::kResultCount; ++resultInt) { 125 DiffRecord::Result result = static_cast<DiffRecord::Result>(resultInt); 126 if (failOnResultType[result]) { 127 printf("[*] "); 128 } else { 129 printf("[_] "); 130 } 131 printContents(fResultsOfType[result], DiffRecord::getResultDescription(result), 132 listFilenames); 133 if (DiffRecord::kCouldNotCompare_Result == result) { 134 printStatus(listFilenames, failOnStatusType); 135 } 136 } 137 printf("(results marked with [*] will cause nonzero return value)\n"); 138 printf("\nnumber of mismatching file pairs: %d\n", fNumMismatches); 139 if (fNumMismatches > 0) { 140 printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV); 141 printf("Largest area mismatch was %.2f%% of pixels\n",fMaxMismatchPercent); 142 } 143 } 144 145 void add (DiffRecord* drp) { 146 uint32_t mismatchValue; 147 148 if (drp->fBase.fFilename.equals(drp->fComparison.fFilename)) { 149 fResultsOfType[drp->fResult].push(new SkString(drp->fBase.fFilename)); 150 } else { 151 SkString* blame = new SkString("("); 152 blame->append(drp->fBase.fFilename); 153 blame->append(", "); 154 blame->append(drp->fComparison.fFilename); 155 blame->append(")"); 156 fResultsOfType[drp->fResult].push(blame); 157 } 158 switch (drp->fResult) { 159 case DiffRecord::kEqualBits_Result: 160 fNumMatches++; 161 break; 162 case DiffRecord::kEqualPixels_Result: 163 fNumMatches++; 164 break; 165 case DiffRecord::kDifferentSizes_Result: 166 fNumMismatches++; 167 break; 168 case DiffRecord::kDifferentPixels_Result: 169 fNumMismatches++; 170 if (drp->fFractionDifference * 100 > fMaxMismatchPercent) { 171 fMaxMismatchPercent = drp->fFractionDifference * 100; 172 } 173 mismatchValue = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG, 174 drp->fMaxMismatchB); 175 if (mismatchValue > fMaxMismatchV) { 176 fMaxMismatchV = mismatchValue; 177 } 178 break; 179 case DiffRecord::kCouldNotCompare_Result: 180 fNumMismatches++; 181 fStatusOfType[drp->fBase.fStatus][drp->fComparison.fStatus].push( 182 new SkString(drp->fBase.fFilename)); 183 break; 184 case DiffRecord::kUnknown_Result: 185 SkDEBUGFAIL("adding uncategorized DiffRecord"); 186 break; 187 default: 188 SkDEBUGFAIL("adding DiffRecord with unhandled fResult value"); 189 break; 190 } 191 } 192 }; 193 194 /// Returns true if string contains any of these substrings. 195 static bool string_contains_any_of(const SkString& string, 196 const StringArray& substrings) { 197 for (int i = 0; i < substrings.count(); i++) { 198 if (string.contains(substrings[i]->c_str())) { 199 return true; 200 } 201 } 202 return false; 203 } 204 205 /// Internal (potentially recursive) implementation of get_file_list. 206 static void get_file_list_subdir(const SkString& rootDir, const SkString& subDir, 207 const StringArray& matchSubstrings, 208 const StringArray& nomatchSubstrings, 209 bool recurseIntoSubdirs, FileArray *files) { 210 bool isSubDirEmpty = subDir.isEmpty(); 211 SkString dir(rootDir); 212 if (!isSubDirEmpty) { 213 dir.append(PATH_DIV_STR); 214 dir.append(subDir); 215 } 216 217 // Iterate over files (not directories) within dir. 218 SkOSFile::Iter fileIterator(dir.c_str()); 219 SkString fileName; 220 while (fileIterator.next(&fileName, false)) { 221 if (fileName.startsWith(".")) { 222 continue; 223 } 224 SkString pathRelativeToRootDir(subDir); 225 if (!isSubDirEmpty) { 226 pathRelativeToRootDir.append(PATH_DIV_STR); 227 } 228 pathRelativeToRootDir.append(fileName); 229 if (string_contains_any_of(pathRelativeToRootDir, matchSubstrings) && 230 !string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) { 231 files->push(new SkString(pathRelativeToRootDir)); 232 } 233 } 234 235 // Recurse into any non-ignored subdirectories. 236 if (recurseIntoSubdirs) { 237 SkOSFile::Iter dirIterator(dir.c_str()); 238 SkString dirName; 239 while (dirIterator.next(&dirName, true)) { 240 if (dirName.startsWith(".")) { 241 continue; 242 } 243 SkString pathRelativeToRootDir(subDir); 244 if (!isSubDirEmpty) { 245 pathRelativeToRootDir.append(PATH_DIV_STR); 246 } 247 pathRelativeToRootDir.append(dirName); 248 if (!string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) { 249 get_file_list_subdir(rootDir, pathRelativeToRootDir, 250 matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, 251 files); 252 } 253 } 254 } 255 } 256 257 /// Iterate over dir and get all files whose filename: 258 /// - matches any of the substrings in matchSubstrings, but... 259 /// - DOES NOT match any of the substrings in nomatchSubstrings 260 /// - DOES NOT start with a dot (.) 261 /// Adds the matching files to the list in *files. 262 static void get_file_list(const SkString& dir, 263 const StringArray& matchSubstrings, 264 const StringArray& nomatchSubstrings, 265 bool recurseIntoSubdirs, FileArray *files) { 266 get_file_list_subdir(dir, SkString(""), 267 matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, 268 files); 269 } 270 271 static void release_file_list(FileArray *files) { 272 files->deleteAll(); 273 } 274 275 /// Comparison routines for qsort, sort by file names. 276 static int compare_file_name_metrics(SkString **lhs, SkString **rhs) { 277 return strcmp((*lhs)->c_str(), (*rhs)->c_str()); 278 } 279 280 class AutoReleasePixels { 281 public: 282 AutoReleasePixels(DiffRecord* drp) 283 : fDrp(drp) { 284 SkASSERT(drp != NULL); 285 } 286 ~AutoReleasePixels() { 287 fDrp->fBase.fBitmap.setPixelRef(NULL); 288 fDrp->fComparison.fBitmap.setPixelRef(NULL); 289 fDrp->fDifference.fBitmap.setPixelRef(NULL); 290 fDrp->fWhite.fBitmap.setPixelRef(NULL); 291 } 292 293 private: 294 DiffRecord* fDrp; 295 }; 296 297 static void get_bounds(DiffResource& resource, const char* name) { 298 if (resource.fBitmap.empty() && !DiffResource::isStatusFailed(resource.fStatus)) { 299 SkAutoDataUnref fileBits(read_file(resource.fFullPath.c_str())); 300 if (NULL == fileBits) { 301 SkDebugf("WARNING: couldn't read %s file <%s>\n", name, resource.fFullPath.c_str()); 302 resource.fStatus = DiffResource::kCouldNotRead_Status; 303 } else { 304 get_bitmap(fileBits, resource, SkImageDecoder::kDecodeBounds_Mode); 305 } 306 } 307 } 308 309 static void get_bounds(DiffRecord& drp) { 310 get_bounds(drp.fBase, "base"); 311 get_bounds(drp.fComparison, "comparison"); 312 } 313 314 /// Creates difference images, returns the number that have a 0 metric. 315 /// If outputDir.isEmpty(), don't write out diff files. 316 static void create_diff_images (DiffMetricProc dmp, 317 const int colorThreshold, 318 RecordArray* differences, 319 const SkString& baseDir, 320 const SkString& comparisonDir, 321 const SkString& outputDir, 322 const StringArray& matchSubstrings, 323 const StringArray& nomatchSubstrings, 324 bool recurseIntoSubdirs, 325 bool getBounds, 326 DiffSummary* summary) { 327 SkASSERT(!baseDir.isEmpty()); 328 SkASSERT(!comparisonDir.isEmpty()); 329 330 FileArray baseFiles; 331 FileArray comparisonFiles; 332 333 get_file_list(baseDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, &baseFiles); 334 get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, 335 &comparisonFiles); 336 337 if (!baseFiles.isEmpty()) { 338 qsort(baseFiles.begin(), baseFiles.count(), sizeof(SkString*), 339 SkCastForQSort(compare_file_name_metrics)); 340 } 341 if (!comparisonFiles.isEmpty()) { 342 qsort(comparisonFiles.begin(), comparisonFiles.count(), 343 sizeof(SkString*), SkCastForQSort(compare_file_name_metrics)); 344 } 345 346 int i = 0; 347 int j = 0; 348 349 while (i < baseFiles.count() && 350 j < comparisonFiles.count()) { 351 352 SkString basePath(baseDir); 353 SkString comparisonPath(comparisonDir); 354 355 DiffRecord *drp = new DiffRecord; 356 int v = strcmp(baseFiles[i]->c_str(), comparisonFiles[j]->c_str()); 357 358 if (v < 0) { 359 // in baseDir, but not in comparisonDir 360 drp->fResult = DiffRecord::kCouldNotCompare_Result; 361 362 basePath.append(*baseFiles[i]); 363 comparisonPath.append(*baseFiles[i]); 364 365 drp->fBase.fFilename = *baseFiles[i]; 366 drp->fBase.fFullPath = basePath; 367 drp->fBase.fStatus = DiffResource::kExists_Status; 368 369 drp->fComparison.fFilename = *baseFiles[i]; 370 drp->fComparison.fFullPath = comparisonPath; 371 drp->fComparison.fStatus = DiffResource::kDoesNotExist_Status; 372 373 ++i; 374 } else if (v > 0) { 375 // in comparisonDir, but not in baseDir 376 drp->fResult = DiffRecord::kCouldNotCompare_Result; 377 378 basePath.append(*comparisonFiles[j]); 379 comparisonPath.append(*comparisonFiles[j]); 380 381 drp->fBase.fFilename = *comparisonFiles[j]; 382 drp->fBase.fFullPath = basePath; 383 drp->fBase.fStatus = DiffResource::kDoesNotExist_Status; 384 385 drp->fComparison.fFilename = *comparisonFiles[j]; 386 drp->fComparison.fFullPath = comparisonPath; 387 drp->fComparison.fStatus = DiffResource::kExists_Status; 388 389 ++j; 390 } else { 391 // Found the same filename in both baseDir and comparisonDir. 392 SkASSERT(DiffRecord::kUnknown_Result == drp->fResult); 393 394 basePath.append(*baseFiles[i]); 395 comparisonPath.append(*comparisonFiles[j]); 396 397 drp->fBase.fFilename = *baseFiles[i]; 398 drp->fBase.fFullPath = basePath; 399 drp->fBase.fStatus = DiffResource::kExists_Status; 400 401 drp->fComparison.fFilename = *comparisonFiles[j]; 402 drp->fComparison.fFullPath = comparisonPath; 403 drp->fComparison.fStatus = DiffResource::kExists_Status; 404 405 SkAutoDataUnref baseFileBits(read_file(drp->fBase.fFullPath.c_str())); 406 if (NULL != baseFileBits) { 407 drp->fBase.fStatus = DiffResource::kRead_Status; 408 } 409 SkAutoDataUnref comparisonFileBits(read_file(drp->fComparison.fFullPath.c_str())); 410 if (NULL != comparisonFileBits) { 411 drp->fComparison.fStatus = DiffResource::kRead_Status; 412 } 413 if (NULL == baseFileBits || NULL == comparisonFileBits) { 414 if (NULL == baseFileBits) { 415 drp->fBase.fStatus = DiffResource::kCouldNotRead_Status; 416 } 417 if (NULL == comparisonFileBits) { 418 drp->fComparison.fStatus = DiffResource::kCouldNotRead_Status; 419 } 420 drp->fResult = DiffRecord::kCouldNotCompare_Result; 421 422 } else if (are_buffers_equal(baseFileBits, comparisonFileBits)) { 423 drp->fResult = DiffRecord::kEqualBits_Result; 424 425 } else { 426 AutoReleasePixels arp(drp); 427 get_bitmap(baseFileBits, drp->fBase, SkImageDecoder::kDecodePixels_Mode); 428 get_bitmap(comparisonFileBits, drp->fComparison, 429 SkImageDecoder::kDecodePixels_Mode); 430 if (DiffResource::kDecoded_Status == drp->fBase.fStatus && 431 DiffResource::kDecoded_Status == drp->fComparison.fStatus) { 432 create_and_write_diff_image(drp, dmp, colorThreshold, 433 outputDir, drp->fBase.fFilename); 434 } else { 435 drp->fResult = DiffRecord::kCouldNotCompare_Result; 436 } 437 } 438 439 ++i; 440 ++j; 441 } 442 443 if (getBounds) { 444 get_bounds(*drp); 445 } 446 SkASSERT(DiffRecord::kUnknown_Result != drp->fResult); 447 differences->push(drp); 448 summary->add(drp); 449 } 450 451 for (; i < baseFiles.count(); ++i) { 452 // files only in baseDir 453 DiffRecord *drp = new DiffRecord(); 454 drp->fBase.fFilename = *baseFiles[i]; 455 drp->fBase.fFullPath = baseDir; 456 drp->fBase.fFullPath.append(drp->fBase.fFilename); 457 drp->fBase.fStatus = DiffResource::kExists_Status; 458 459 drp->fComparison.fFilename = *baseFiles[i]; 460 drp->fComparison.fFullPath = comparisonDir; 461 drp->fComparison.fFullPath.append(drp->fComparison.fFilename); 462 drp->fComparison.fStatus = DiffResource::kDoesNotExist_Status; 463 464 drp->fResult = DiffRecord::kCouldNotCompare_Result; 465 if (getBounds) { 466 get_bounds(*drp); 467 } 468 differences->push(drp); 469 summary->add(drp); 470 } 471 472 for (; j < comparisonFiles.count(); ++j) { 473 // files only in comparisonDir 474 DiffRecord *drp = new DiffRecord(); 475 drp->fBase.fFilename = *comparisonFiles[j]; 476 drp->fBase.fFullPath = baseDir; 477 drp->fBase.fFullPath.append(drp->fBase.fFilename); 478 drp->fBase.fStatus = DiffResource::kDoesNotExist_Status; 479 480 drp->fComparison.fFilename = *comparisonFiles[j]; 481 drp->fComparison.fFullPath = comparisonDir; 482 drp->fComparison.fFullPath.append(drp->fComparison.fFilename); 483 drp->fComparison.fStatus = DiffResource::kExists_Status; 484 485 drp->fResult = DiffRecord::kCouldNotCompare_Result; 486 if (getBounds) { 487 get_bounds(*drp); 488 } 489 differences->push(drp); 490 summary->add(drp); 491 } 492 493 release_file_list(&baseFiles); 494 release_file_list(&comparisonFiles); 495 } 496 497 static void usage (char * argv0) { 498 SkDebugf("Skia baseline image diff tool\n"); 499 SkDebugf("\n" 500 "Usage: \n" 501 " %s <baseDir> <comparisonDir> [outputDir] \n", argv0); 502 SkDebugf( 503 "\nArguments:" 504 "\n --failonresult <result>: After comparing all file pairs, exit with nonzero" 505 "\n return code (number of file pairs yielding this" 506 "\n result) if any file pairs yielded this result." 507 "\n This flag may be repeated, in which case the" 508 "\n return code will be the number of fail pairs" 509 "\n yielding ANY of these results." 510 "\n --failonstatus <baseStatus> <comparisonStatus>: exit with nonzero return" 511 "\n code if any file pairs yielded this status." 512 "\n --help: display this info" 513 "\n --listfilenames: list all filenames for each result type in stdout" 514 "\n --match <substring>: compare files whose filenames contain this substring;" 515 "\n if unspecified, compare ALL files." 516 "\n this flag may be repeated." 517 "\n --nodiffs: don't write out image diffs or index.html, just generate" 518 "\n report on stdout" 519 "\n --nomatch <substring>: regardless of --match, DO NOT compare files whose" 520 "\n filenames contain this substring." 521 "\n this flag may be repeated." 522 "\n --noprintdirs: do not print the directories used." 523 "\n --norecurse: do not recurse into subdirectories." 524 "\n --sortbymaxmismatch: sort by worst color channel mismatch;" 525 "\n break ties with -sortbymismatch" 526 "\n --sortbymismatch: sort by average color channel mismatch" 527 "\n --threshold <n>: only report differences > n (per color channel) [default 0]" 528 "\n --weighted: sort by # pixels different weighted by color difference" 529 "\n" 530 "\n baseDir: directory to read baseline images from." 531 "\n comparisonDir: directory to read comparison images from" 532 "\n outputDir: directory to write difference images and index.html to;" 533 "\n defaults to comparisonDir" 534 "\n" 535 "\nIf no sort is specified, it will sort by fraction of pixels mismatching." 536 "\n"); 537 } 538 539 const int kNoError = 0; 540 const int kGenericError = -1; 541 542 int tool_main(int argc, char** argv); 543 int tool_main(int argc, char** argv) { 544 DiffMetricProc diffProc = compute_diff_pmcolor; 545 int (*sortProc)(const void*, const void*) = compare<CompareDiffMetrics>; 546 547 // Maximum error tolerated in any one color channel in any one pixel before 548 // a difference is reported. 549 int colorThreshold = 0; 550 SkString baseDir; 551 SkString comparisonDir; 552 SkString outputDir; 553 554 StringArray matchSubstrings; 555 StringArray nomatchSubstrings; 556 557 bool generateDiffs = true; 558 bool listFilenames = false; 559 bool printDirNames = true; 560 bool recurseIntoSubdirs = true; 561 562 RecordArray differences; 563 DiffSummary summary; 564 565 bool failOnResultType[DiffRecord::kResultCount]; 566 for (int i = 0; i < DiffRecord::kResultCount; i++) { 567 failOnResultType[i] = false; 568 } 569 570 bool failOnStatusType[DiffResource::kStatusCount][DiffResource::kStatusCount]; 571 for (int base = 0; base < DiffResource::kStatusCount; ++base) { 572 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { 573 failOnStatusType[base][comparison] = false; 574 } 575 } 576 577 int i; 578 int numUnflaggedArguments = 0; 579 for (i = 1; i < argc; i++) { 580 if (!strcmp(argv[i], "--failonresult")) { 581 if (argc == ++i) { 582 SkDebugf("failonresult expects one argument.\n"); 583 continue; 584 } 585 DiffRecord::Result type = DiffRecord::getResultByName(argv[i]); 586 if (type != DiffRecord::kResultCount) { 587 failOnResultType[type] = true; 588 } else { 589 SkDebugf("ignoring unrecognized result <%s>\n", argv[i]); 590 } 591 continue; 592 } 593 if (!strcmp(argv[i], "--failonstatus")) { 594 if (argc == ++i) { 595 SkDebugf("failonstatus missing base status.\n"); 596 continue; 597 } 598 bool baseStatuses[DiffResource::kStatusCount]; 599 if (!DiffResource::getMatchingStatuses(argv[i], baseStatuses)) { 600 SkDebugf("unrecognized base status <%s>\n", argv[i]); 601 } 602 603 if (argc == ++i) { 604 SkDebugf("failonstatus missing comparison status.\n"); 605 continue; 606 } 607 bool comparisonStatuses[DiffResource::kStatusCount]; 608 if (!DiffResource::getMatchingStatuses(argv[i], comparisonStatuses)) { 609 SkDebugf("unrecognized comarison status <%s>\n", argv[i]); 610 } 611 612 for (int base = 0; base < DiffResource::kStatusCount; ++base) { 613 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { 614 failOnStatusType[base][comparison] |= 615 baseStatuses[base] && comparisonStatuses[comparison]; 616 } 617 } 618 continue; 619 } 620 if (!strcmp(argv[i], "--help")) { 621 usage(argv[0]); 622 return kNoError; 623 } 624 if (!strcmp(argv[i], "--listfilenames")) { 625 listFilenames = true; 626 continue; 627 } 628 if (!strcmp(argv[i], "--match")) { 629 matchSubstrings.push(new SkString(argv[++i])); 630 continue; 631 } 632 if (!strcmp(argv[i], "--nodiffs")) { 633 generateDiffs = false; 634 continue; 635 } 636 if (!strcmp(argv[i], "--nomatch")) { 637 nomatchSubstrings.push(new SkString(argv[++i])); 638 continue; 639 } 640 if (!strcmp(argv[i], "--noprintdirs")) { 641 printDirNames = false; 642 continue; 643 } 644 if (!strcmp(argv[i], "--norecurse")) { 645 recurseIntoSubdirs = false; 646 continue; 647 } 648 if (!strcmp(argv[i], "--sortbymaxmismatch")) { 649 sortProc = compare<CompareDiffMaxMismatches>; 650 continue; 651 } 652 if (!strcmp(argv[i], "--sortbymismatch")) { 653 sortProc = compare<CompareDiffMeanMismatches>; 654 continue; 655 } 656 if (!strcmp(argv[i], "--threshold")) { 657 colorThreshold = atoi(argv[++i]); 658 continue; 659 } 660 if (!strcmp(argv[i], "--weighted")) { 661 sortProc = compare<CompareDiffWeighted>; 662 continue; 663 } 664 if (argv[i][0] != '-') { 665 switch (numUnflaggedArguments++) { 666 case 0: 667 baseDir.set(argv[i]); 668 continue; 669 case 1: 670 comparisonDir.set(argv[i]); 671 continue; 672 case 2: 673 outputDir.set(argv[i]); 674 continue; 675 default: 676 SkDebugf("extra unflagged argument <%s>\n", argv[i]); 677 usage(argv[0]); 678 return kGenericError; 679 } 680 } 681 682 SkDebugf("Unrecognized argument <%s>\n", argv[i]); 683 usage(argv[0]); 684 return kGenericError; 685 } 686 687 if (numUnflaggedArguments == 2) { 688 outputDir = comparisonDir; 689 } else if (numUnflaggedArguments != 3) { 690 usage(argv[0]); 691 return kGenericError; 692 } 693 694 if (!baseDir.endsWith(PATH_DIV_STR)) { 695 baseDir.append(PATH_DIV_STR); 696 } 697 if (printDirNames) { 698 printf("baseDir is [%s]\n", baseDir.c_str()); 699 } 700 701 if (!comparisonDir.endsWith(PATH_DIV_STR)) { 702 comparisonDir.append(PATH_DIV_STR); 703 } 704 if (printDirNames) { 705 printf("comparisonDir is [%s]\n", comparisonDir.c_str()); 706 } 707 708 if (!outputDir.endsWith(PATH_DIV_STR)) { 709 outputDir.append(PATH_DIV_STR); 710 } 711 if (generateDiffs) { 712 if (printDirNames) { 713 printf("writing diffs to outputDir is [%s]\n", outputDir.c_str()); 714 } 715 } else { 716 if (printDirNames) { 717 printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str()); 718 } 719 outputDir.set(""); 720 } 721 722 // If no matchSubstrings were specified, match ALL strings 723 // (except for whatever nomatchSubstrings were specified, if any). 724 if (matchSubstrings.isEmpty()) { 725 matchSubstrings.push(new SkString("")); 726 } 727 728 create_diff_images(diffProc, colorThreshold, &differences, 729 baseDir, comparisonDir, outputDir, 730 matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, generateDiffs, 731 &summary); 732 summary.print(listFilenames, failOnResultType, failOnStatusType); 733 734 if (differences.count()) { 735 qsort(differences.begin(), differences.count(), 736 sizeof(DiffRecord*), sortProc); 737 } 738 739 if (generateDiffs) { 740 print_diff_page(summary.fNumMatches, colorThreshold, differences, 741 baseDir, comparisonDir, outputDir); 742 } 743 744 for (i = 0; i < differences.count(); i++) { 745 delete differences[i]; 746 } 747 matchSubstrings.deleteAll(); 748 nomatchSubstrings.deleteAll(); 749 750 int num_failing_results = 0; 751 for (int i = 0; i < DiffRecord::kResultCount; i++) { 752 if (failOnResultType[i]) { 753 num_failing_results += summary.fResultsOfType[i].count(); 754 } 755 } 756 if (!failOnResultType[DiffRecord::kCouldNotCompare_Result]) { 757 for (int base = 0; base < DiffResource::kStatusCount; ++base) { 758 for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { 759 if (failOnStatusType[base][comparison]) { 760 num_failing_results += summary.fStatusOfType[base][comparison].count(); 761 } 762 } 763 } 764 } 765 766 // On Linux (and maybe other platforms too), any results outside of the 767 // range [0...255] are wrapped (mod 256). Do the conversion ourselves, to 768 // make sure that we only return 0 when there were no failures. 769 return (num_failing_results > 255) ? 255 : num_failing_results; 770 } 771 772 #if !defined SK_BUILD_FOR_IOS 773 int main(int argc, char * const argv[]) { 774 return tool_main(argc, (char**) argv); 775 } 776 #endif 777