int tool_main(int argc, char** argv) { DiffMetricProc diffProc = compute_diff_pmcolor; int (*sortProc)(const void*, const void*) = compare<CompareDiffMetrics>; // Maximum error tolerated in any one color channel in any one pixel before // a difference is reported. int colorThreshold = 0; SkString baseDir; SkString comparisonDir; SkString outputDir; StringArray matchSubstrings; StringArray nomatchSubstrings; bool generateDiffs = true; bool listFilenames = false; bool printDirNames = true; bool recurseIntoSubdirs = true; bool verbose = false; bool listFailingBase = false; RecordArray differences; DiffSummary summary; bool failOnResultType[DiffRecord::kResultCount]; for (int i = 0; i < DiffRecord::kResultCount; i++) { failOnResultType[i] = false; } bool failOnStatusType[DiffResource::kStatusCount][DiffResource::kStatusCount]; for (int base = 0; base < DiffResource::kStatusCount; ++base) { for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { failOnStatusType[base][comparison] = false; } } int i; int numUnflaggedArguments = 0; for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "--failonresult")) { if (argc == ++i) { SkDebugf("failonresult expects one argument.\n"); continue; } DiffRecord::Result type = DiffRecord::getResultByName(argv[i]); if (type != DiffRecord::kResultCount) { failOnResultType[type] = true; } else { SkDebugf("ignoring unrecognized result <%s>\n", argv[i]); } continue; } if (!strcmp(argv[i], "--failonstatus")) { if (argc == ++i) { SkDebugf("failonstatus missing base status.\n"); continue; } bool baseStatuses[DiffResource::kStatusCount]; if (!DiffResource::getMatchingStatuses(argv[i], baseStatuses)) { SkDebugf("unrecognized base status <%s>\n", argv[i]); } if (argc == ++i) { SkDebugf("failonstatus missing comparison status.\n"); continue; } bool comparisonStatuses[DiffResource::kStatusCount]; if (!DiffResource::getMatchingStatuses(argv[i], comparisonStatuses)) { SkDebugf("unrecognized comarison status <%s>\n", argv[i]); } for (int base = 0; base < DiffResource::kStatusCount; ++base) { for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { failOnStatusType[base][comparison] |= baseStatuses[base] && comparisonStatuses[comparison]; } } continue; } if (!strcmp(argv[i], "--help")) { usage(argv[0]); return kNoError; } if (!strcmp(argv[i], "--listfilenames")) { listFilenames = true; continue; } if (!strcmp(argv[i], "--verbose")) { verbose = true; continue; } if (!strcmp(argv[i], "--match")) { matchSubstrings.push(new SkString(argv[++i])); continue; } if (!strcmp(argv[i], "--nodiffs")) { generateDiffs = false; continue; } if (!strcmp(argv[i], "--nomatch")) { nomatchSubstrings.push(new SkString(argv[++i])); continue; } if (!strcmp(argv[i], "--noprintdirs")) { printDirNames = false; continue; } if (!strcmp(argv[i], "--norecurse")) { recurseIntoSubdirs = false; continue; } if (!strcmp(argv[i], "--sortbymaxmismatch")) { sortProc = compare<CompareDiffMaxMismatches>; continue; } if (!strcmp(argv[i], "--sortbymismatch")) { sortProc = compare<CompareDiffMeanMismatches>; continue; } if (!strcmp(argv[i], "--threshold")) { colorThreshold = atoi(argv[++i]); continue; } if (!strcmp(argv[i], "--weighted")) { sortProc = compare<CompareDiffWeighted>; continue; } if (argv[i][0] != '-') { switch (numUnflaggedArguments++) { case 0: baseDir.set(argv[i]); continue; case 1: comparisonDir.set(argv[i]); continue; case 2: outputDir.set(argv[i]); continue; default: SkDebugf("extra unflagged argument <%s>\n", argv[i]); usage(argv[0]); return kGenericError; } } if (!strcmp(argv[i], "--listFailingBase")) { listFailingBase = true; continue; } SkDebugf("Unrecognized argument <%s>\n", argv[i]); usage(argv[0]); return kGenericError; } if (numUnflaggedArguments == 2) { outputDir = comparisonDir; } else if (numUnflaggedArguments != 3) { usage(argv[0]); return kGenericError; } if (!baseDir.endsWith(PATH_DIV_STR)) { baseDir.append(PATH_DIV_STR); } if (printDirNames) { printf("baseDir is [%s]\n", baseDir.c_str()); } if (!comparisonDir.endsWith(PATH_DIV_STR)) { comparisonDir.append(PATH_DIV_STR); } if (printDirNames) { printf("comparisonDir is [%s]\n", comparisonDir.c_str()); } if (!outputDir.endsWith(PATH_DIV_STR)) { outputDir.append(PATH_DIV_STR); } if (generateDiffs) { if (printDirNames) { printf("writing diffs to outputDir is [%s]\n", outputDir.c_str()); } } else { if (printDirNames) { printf("not writing any diffs to outputDir [%s]\n", outputDir.c_str()); } outputDir.set(""); } // If no matchSubstrings were specified, match ALL strings // (except for whatever nomatchSubstrings were specified, if any). if (matchSubstrings.isEmpty()) { matchSubstrings.push(new SkString("")); } create_diff_images(diffProc, colorThreshold, &differences, baseDir, comparisonDir, outputDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, generateDiffs, verbose, &summary); summary.print(listFilenames, failOnResultType, failOnStatusType); if (listFailingBase) { summary.printfFailingBaseNames("\n"); } if (differences.count()) { qsort(differences.begin(), differences.count(), sizeof(DiffRecord*), sortProc); } if (generateDiffs) { print_diff_page(summary.fNumMatches, colorThreshold, differences, baseDir, comparisonDir, outputDir); } for (i = 0; i < differences.count(); i++) { delete differences[i]; } matchSubstrings.deleteAll(); nomatchSubstrings.deleteAll(); int num_failing_results = 0; for (int i = 0; i < DiffRecord::kResultCount; i++) { if (failOnResultType[i]) { num_failing_results += summary.fResultsOfType[i].count(); } } if (!failOnResultType[DiffRecord::kCouldNotCompare_Result]) { for (int base = 0; base < DiffResource::kStatusCount; ++base) { for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { if (failOnStatusType[base][comparison]) { num_failing_results += summary.fStatusOfType[base][comparison].count(); } } } } // On Linux (and maybe other platforms too), any results outside of the // range [0...255] are wrapped (mod 256). Do the conversion ourselves, to // make sure that we only return 0 when there were no failures. return (num_failing_results > 255) ? 255 : num_failing_results; }
int tool_main(int argc, char** argv) { DiffMetricProc diffProc = compute_diff_pmcolor; // Maximum error tolerated in any one color channel in any one pixel before // a difference is reported. int colorThreshold = 0; SkString baseFile; SkString baseLabel; SkString comparisonFile; SkString comparisonLabel; SkString outputDir; bool listFilenames = false; bool failOnResultType[DiffRecord::kResultCount]; for (int i = 0; i < DiffRecord::kResultCount; i++) { failOnResultType[i] = false; } bool failOnStatusType[DiffResource::kStatusCount][DiffResource::kStatusCount]; for (int base = 0; base < DiffResource::kStatusCount; ++base) { for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { failOnStatusType[base][comparison] = false; } } int i; int numUnflaggedArguments = 0; int numLabelArguments = 0; for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "--failonresult")) { if (argc == ++i) { SkDebugf("failonresult expects one argument.\n"); continue; } DiffRecord::Result type = DiffRecord::getResultByName(argv[i]); if (type != DiffRecord::kResultCount) { failOnResultType[type] = true; } else { SkDebugf("ignoring unrecognized result <%s>\n", argv[i]); } continue; } if (!strcmp(argv[i], "--failonstatus")) { if (argc == ++i) { SkDebugf("failonstatus missing base status.\n"); continue; } bool baseStatuses[DiffResource::kStatusCount]; if (!DiffResource::getMatchingStatuses(argv[i], baseStatuses)) { SkDebugf("unrecognized base status <%s>\n", argv[i]); } if (argc == ++i) { SkDebugf("failonstatus missing comparison status.\n"); continue; } bool comparisonStatuses[DiffResource::kStatusCount]; if (!DiffResource::getMatchingStatuses(argv[i], comparisonStatuses)) { SkDebugf("unrecognized comarison status <%s>\n", argv[i]); } for (int base = 0; base < DiffResource::kStatusCount; ++base) { for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { failOnStatusType[base][comparison] |= baseStatuses[base] && comparisonStatuses[comparison]; } } continue; } if (!strcmp(argv[i], "--help")) { usage(argv[0]); return kNoError; } if (!strcmp(argv[i], "--listfilenames")) { listFilenames = true; continue; } if (!strcmp(argv[i], "--outputdir")) { if (argc == ++i) { SkDebugf("outputdir expects one argument.\n"); continue; } outputDir.set(argv[i]); continue; } if (!strcmp(argv[i], "--threshold")) { colorThreshold = atoi(argv[++i]); continue; } if (!strcmp(argv[i], "-u")) { //we don't produce unified diffs, ignore parameter to work with svn diff continue; } if (!strcmp(argv[i], "-L")) { if (argc == ++i) { SkDebugf("label expects one argument.\n"); continue; } switch (numLabelArguments++) { case 0: baseLabel.set(argv[i]); continue; case 1: comparisonLabel.set(argv[i]); continue; default: SkDebugf("extra label argument <%s>\n", argv[i]); usage(argv[0]); return kGenericError; } continue; } if (argv[i][0] != '-') { switch (numUnflaggedArguments++) { case 0: baseFile.set(argv[i]); continue; case 1: comparisonFile.set(argv[i]); continue; default: SkDebugf("extra unflagged argument <%s>\n", argv[i]); usage(argv[0]); return kGenericError; } } SkDebugf("Unrecognized argument <%s>\n", argv[i]); usage(argv[0]); return kGenericError; } if (numUnflaggedArguments != 2) { usage(argv[0]); return kGenericError; } if (listFilenames) { printf("Base file is [%s]\n", baseFile.c_str()); } if (listFilenames) { printf("Comparison file is [%s]\n", comparisonFile.c_str()); } if (outputDir.isEmpty()) { if (listFilenames) { printf("Not writing any diffs. No output dir specified.\n"); } } else { if (!outputDir.endsWith(PATH_DIV_STR)) { outputDir.append(PATH_DIV_STR); } if (listFilenames) { printf("Writing diffs. Output dir is [%s]\n", outputDir.c_str()); } } // Some obscure documentation about diff/patch labels: // // Posix says the format is: <filename><tab><date> // It also states that if a filename contains <tab> or <newline> // the result is implementation defined // // Svn diff --diff-cmd provides labels of the form: <filename><tab><revision> // // Git diff --ext-diff does not supply arguments compatible with diff. // However, it does provide the filename directly. // skimagediff_git.sh: skimagediff %2 %5 -L "%1\t(%3)" -L "%1\t(%6)" // // Git difftool sets $LOCAL, $REMOTE, $MERGED, and $BASE instead of command line parameters. // difftool.<>.cmd: skimagediff $LOCAL $REMOTE -L "$MERGED\t(local)" -L "$MERGED\t(remote)" // // Diff will write any specified label verbatim. Without a specified label diff will write // <filename><tab><date> // However, diff will encode the filename as a cstring if the filename contains // Any of <space> or <double quote> // A char less than 32 // Any escapable character \\, \a, \b, \t, \n, \v, \f, \r // // Patch decodes: // If first <non-white-space> is <double quote>, parse filename from cstring. // If there is a <tab> after the first <non-white-space>, filename is // [first <non-white-space>, the next run of <white-space> with an embedded <tab>). // Otherwise the filename is [first <non-space>, the next <white-space>). // // The filename /dev/null means the file does not exist (used in adds and deletes). // Considering the above, skimagediff will consider the contents of a -L parameter as // <filename>(\t<specifier>)? SkString outputFile; if (baseLabel.isEmpty()) { baseLabel.set(baseFile); outputFile = baseLabel; } else { const char* baseLabelCstr = baseLabel.c_str(); const char* tab = strchr(baseLabelCstr, '\t'); if (nullptr == tab) { outputFile = baseLabel; } else { outputFile.set(baseLabelCstr, tab - baseLabelCstr); } } if (comparisonLabel.isEmpty()) { comparisonLabel.set(comparisonFile); } printf("Base: %s\n", baseLabel.c_str()); printf("Comparison: %s\n", comparisonLabel.c_str()); DiffRecord dr; create_diff_images(diffProc, colorThreshold, baseFile, comparisonFile, outputDir, outputFile, &dr); if (DiffResource::isStatusFailed(dr.fBase.fStatus)) { printf("Base %s.\n", DiffResource::getStatusDescription(dr.fBase.fStatus)); } if (DiffResource::isStatusFailed(dr.fComparison.fStatus)) { printf("Comparison %s.\n", DiffResource::getStatusDescription(dr.fComparison.fStatus)); } printf("Base and Comparison %s.\n", DiffRecord::getResultDescription(dr.fResult)); if (DiffRecord::kDifferentPixels_Result == dr.fResult) { printf("%.4f%% of pixels differ", 100 * dr.fFractionDifference); printf(" (%.4f%% weighted)", 100 * dr.fWeightedFraction); if (dr.fFractionDifference < 0.01) { printf(" %d pixels", static_cast<int>(dr.fFractionDifference * dr.fBase.fBitmap.width() * dr.fBase.fBitmap.height())); } printf("\nAverage color mismatch: "); printf("%d", static_cast<int>(MAX3(dr.fAverageMismatchR, dr.fAverageMismatchG, dr.fAverageMismatchB))); printf("\nMax color mismatch: "); printf("%d", MAX3(dr.fMaxMismatchR, dr.fMaxMismatchG, dr.fMaxMismatchB)); printf("\n"); } printf("\n"); int num_failing_results = 0; if (failOnResultType[dr.fResult]) { ++num_failing_results; } if (failOnStatusType[dr.fBase.fStatus][dr.fComparison.fStatus]) { ++num_failing_results; } return num_failing_results; }