diff options
Diffstat (limited to 'tools/skdiff/skdiff_main.cpp')
-rw-r--r-- | tools/skdiff/skdiff_main.cpp | 862 |
1 files changed, 862 insertions, 0 deletions
diff --git a/tools/skdiff/skdiff_main.cpp b/tools/skdiff/skdiff_main.cpp new file mode 100644 index 0000000000..c51cd28d78 --- /dev/null +++ b/tools/skdiff/skdiff_main.cpp @@ -0,0 +1,862 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "skdiff.h" +#include "skdiff_html.h" +#include "skdiff_utils.h" +#include "SkBitmap.h" +#include "SkData.h" +#include "SkForceLinking.h" +#include "SkImageEncoder.h" +#include "SkOSFile.h" +#include "SkStream.h" +#include "../private/SkTDArray.h" +#include "../private/SkTSearch.h" + +#include <stdlib.h> + +__SK_FORCE_IMAGE_DECODER_LINKING; + +/** + * skdiff + * + * Given three directory names, expects to find identically-named files in + * each of the first two; the first are treated as a set of baseline, + * the second a set of variant images, and a diff image is written into the + * third directory for each pair. + * Creates an index.html in the current third directory to compare each + * pair that does not match exactly. + * Recursively descends directories, unless run with --norecurse. + * + * Returns zero exit code if all images match across baseDir and comparisonDir. + */ + +typedef SkTDArray<SkString*> StringArray; +typedef StringArray FileArray; + +static void add_unique_basename(StringArray* array, const SkString& filename) { + // trim off dirs + const char* src = filename.c_str(); + const char* trimmed = strrchr(src, SkPATH_SEPARATOR); + if (trimmed) { + trimmed += 1; // skip the separator + } else { + trimmed = src; + } + const char* end = strrchr(trimmed, '.'); + if (!end) { + end = trimmed + strlen(trimmed); + } + SkString result(trimmed, end - trimmed); + + // only add unique entries + for (int i = 0; i < array->count(); ++i) { + if (*array->getAt(i) == result) { + return; + } + } + *array->append() = new SkString(result); +} + +struct DiffSummary { + DiffSummary () + : fNumMatches(0) + , fNumMismatches(0) + , fMaxMismatchV(0) + , fMaxMismatchPercent(0) { } + + ~DiffSummary() { + for (int i = 0; i < DiffRecord::kResultCount; ++i) { + fResultsOfType[i].deleteAll(); + } + for (int base = 0; base < DiffResource::kStatusCount; ++base) { + for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { + fStatusOfType[base][comparison].deleteAll(); + } + } + } + + uint32_t fNumMatches; + uint32_t fNumMismatches; + uint32_t fMaxMismatchV; + float fMaxMismatchPercent; + + FileArray fResultsOfType[DiffRecord::kResultCount]; + FileArray fStatusOfType[DiffResource::kStatusCount][DiffResource::kStatusCount]; + + StringArray fFailedBaseNames[DiffRecord::kResultCount]; + + void printContents(const FileArray& fileArray, + const char* baseStatus, const char* comparisonStatus, + bool listFilenames) { + int n = fileArray.count(); + printf("%d file pairs %s in baseDir and %s in comparisonDir", + n, baseStatus, comparisonStatus); + if (listFilenames) { + printf(": "); + for (int i = 0; i < n; ++i) { + printf("%s ", fileArray[i]->c_str()); + } + } + printf("\n"); + } + + void printStatus(bool listFilenames, + bool failOnStatusType[DiffResource::kStatusCount] + [DiffResource::kStatusCount]) { + typedef DiffResource::Status Status; + + for (int base = 0; base < DiffResource::kStatusCount; ++base) { + Status baseStatus = static_cast<Status>(base); + for (int comparison = 0; comparison < DiffResource::kStatusCount; ++comparison) { + Status comparisonStatus = static_cast<Status>(comparison); + const FileArray& fileArray = fStatusOfType[base][comparison]; + if (fileArray.count() > 0) { + if (failOnStatusType[base][comparison]) { + printf(" [*] "); + } else { + printf(" [_] "); + } + printContents(fileArray, + DiffResource::getStatusDescription(baseStatus), + DiffResource::getStatusDescription(comparisonStatus), + listFilenames); + } + } + } + } + + // Print a line about the contents of this FileArray to stdout. + void printContents(const FileArray& fileArray, const char* headerText, bool listFilenames) { + int n = fileArray.count(); + printf("%d file pairs %s", n, headerText); + if (listFilenames) { + printf(": "); + for (int i = 0; i < n; ++i) { + printf("%s ", fileArray[i]->c_str()); + } + } + printf("\n"); + } + + void print(bool listFilenames, bool failOnResultType[DiffRecord::kResultCount], + bool failOnStatusType[DiffResource::kStatusCount] + [DiffResource::kStatusCount]) { + printf("\ncompared %d file pairs:\n", fNumMatches + fNumMismatches); + for (int resultInt = 0; resultInt < DiffRecord::kResultCount; ++resultInt) { + DiffRecord::Result result = static_cast<DiffRecord::Result>(resultInt); + if (failOnResultType[result]) { + printf("[*] "); + } else { + printf("[_] "); + } + printContents(fResultsOfType[result], DiffRecord::getResultDescription(result), + listFilenames); + if (DiffRecord::kCouldNotCompare_Result == result) { + printStatus(listFilenames, failOnStatusType); + } + } + printf("(results marked with [*] will cause nonzero return value)\n"); + printf("\nnumber of mismatching file pairs: %d\n", fNumMismatches); + if (fNumMismatches > 0) { + printf("Maximum pixel intensity mismatch %d\n", fMaxMismatchV); + printf("Largest area mismatch was %.2f%% of pixels\n",fMaxMismatchPercent); + } + } + + void printfFailingBaseNames(const char separator[]) { + for (int resultInt = 0; resultInt < DiffRecord::kResultCount; ++resultInt) { + const StringArray& array = fFailedBaseNames[resultInt]; + if (array.count()) { + printf("%s [%d]%s", DiffRecord::ResultNames[resultInt], array.count(), separator); + for (int j = 0; j < array.count(); ++j) { + printf("%s%s", array[j]->c_str(), separator); + } + printf("\n"); + } + } + } + + void add (DiffRecord* drp) { + uint32_t mismatchValue; + + if (drp->fBase.fFilename.equals(drp->fComparison.fFilename)) { + fResultsOfType[drp->fResult].push(new SkString(drp->fBase.fFilename)); + } else { + SkString* blame = new SkString("("); + blame->append(drp->fBase.fFilename); + blame->append(", "); + blame->append(drp->fComparison.fFilename); + blame->append(")"); + fResultsOfType[drp->fResult].push(blame); + } + switch (drp->fResult) { + case DiffRecord::kEqualBits_Result: + fNumMatches++; + break; + case DiffRecord::kEqualPixels_Result: + fNumMatches++; + break; + case DiffRecord::kDifferentSizes_Result: + fNumMismatches++; + break; + case DiffRecord::kDifferentPixels_Result: + fNumMismatches++; + if (drp->fFractionDifference * 100 > fMaxMismatchPercent) { + fMaxMismatchPercent = drp->fFractionDifference * 100; + } + mismatchValue = MAX3(drp->fMaxMismatchR, drp->fMaxMismatchG, + drp->fMaxMismatchB); + if (mismatchValue > fMaxMismatchV) { + fMaxMismatchV = mismatchValue; + } + break; + case DiffRecord::kCouldNotCompare_Result: + fNumMismatches++; + fStatusOfType[drp->fBase.fStatus][drp->fComparison.fStatus].push( + new SkString(drp->fBase.fFilename)); + break; + case DiffRecord::kUnknown_Result: + SkDEBUGFAIL("adding uncategorized DiffRecord"); + break; + default: + SkDEBUGFAIL("adding DiffRecord with unhandled fResult value"); + break; + } + + switch (drp->fResult) { + case DiffRecord::kEqualBits_Result: + case DiffRecord::kEqualPixels_Result: + break; + default: + add_unique_basename(&fFailedBaseNames[drp->fResult], drp->fBase.fFilename); + break; + } + } +}; + +/// Returns true if string contains any of these substrings. +static bool string_contains_any_of(const SkString& string, + const StringArray& substrings) { + for (int i = 0; i < substrings.count(); i++) { + if (string.contains(substrings[i]->c_str())) { + return true; + } + } + return false; +} + +/// Internal (potentially recursive) implementation of get_file_list. +static void get_file_list_subdir(const SkString& rootDir, const SkString& subDir, + const StringArray& matchSubstrings, + const StringArray& nomatchSubstrings, + bool recurseIntoSubdirs, FileArray *files) { + bool isSubDirEmpty = subDir.isEmpty(); + SkString dir(rootDir); + if (!isSubDirEmpty) { + dir.append(PATH_DIV_STR); + dir.append(subDir); + } + + // Iterate over files (not directories) within dir. + SkOSFile::Iter fileIterator(dir.c_str()); + SkString fileName; + while (fileIterator.next(&fileName, false)) { + if (fileName.startsWith(".")) { + continue; + } + SkString pathRelativeToRootDir(subDir); + if (!isSubDirEmpty) { + pathRelativeToRootDir.append(PATH_DIV_STR); + } + pathRelativeToRootDir.append(fileName); + if (string_contains_any_of(pathRelativeToRootDir, matchSubstrings) && + !string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) { + files->push(new SkString(pathRelativeToRootDir)); + } + } + + // Recurse into any non-ignored subdirectories. + if (recurseIntoSubdirs) { + SkOSFile::Iter dirIterator(dir.c_str()); + SkString dirName; + while (dirIterator.next(&dirName, true)) { + if (dirName.startsWith(".")) { + continue; + } + SkString pathRelativeToRootDir(subDir); + if (!isSubDirEmpty) { + pathRelativeToRootDir.append(PATH_DIV_STR); + } + pathRelativeToRootDir.append(dirName); + if (!string_contains_any_of(pathRelativeToRootDir, nomatchSubstrings)) { + get_file_list_subdir(rootDir, pathRelativeToRootDir, + matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, + files); + } + } + } +} + +/// Iterate over dir and get all files whose filename: +/// - matches any of the substrings in matchSubstrings, but... +/// - DOES NOT match any of the substrings in nomatchSubstrings +/// - DOES NOT start with a dot (.) +/// Adds the matching files to the list in *files. +static void get_file_list(const SkString& dir, + const StringArray& matchSubstrings, + const StringArray& nomatchSubstrings, + bool recurseIntoSubdirs, FileArray *files) { + get_file_list_subdir(dir, SkString(""), + matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, + files); +} + +static void release_file_list(FileArray *files) { + files->deleteAll(); +} + +/// Comparison routines for qsort, sort by file names. +static int compare_file_name_metrics(SkString **lhs, SkString **rhs) { + return strcmp((*lhs)->c_str(), (*rhs)->c_str()); +} + +class AutoReleasePixels { +public: + AutoReleasePixels(DiffRecord* drp) + : fDrp(drp) { + SkASSERT(drp != nullptr); + } + ~AutoReleasePixels() { + fDrp->fBase.fBitmap.setPixelRef(nullptr); + fDrp->fComparison.fBitmap.setPixelRef(nullptr); + fDrp->fDifference.fBitmap.setPixelRef(nullptr); + fDrp->fWhite.fBitmap.setPixelRef(nullptr); + } + +private: + DiffRecord* fDrp; +}; + +static void get_bounds(DiffResource& resource, const char* name) { + if (resource.fBitmap.empty() && !DiffResource::isStatusFailed(resource.fStatus)) { + sk_sp<SkData> fileBits(read_file(resource.fFullPath.c_str())); + if (fileBits) { + get_bitmap(fileBits, resource, true); + } else { + SkDebugf("WARNING: couldn't read %s file <%s>\n", name, resource.fFullPath.c_str()); + resource.fStatus = DiffResource::kCouldNotRead_Status; + } + } +} + +static void get_bounds(DiffRecord& drp) { + get_bounds(drp.fBase, "base"); + get_bounds(drp.fComparison, "comparison"); +} + +#ifdef SK_OS_WIN +#define ANSI_COLOR_RED "" +#define ANSI_COLOR_GREEN "" +#define ANSI_COLOR_YELLOW "" +#define ANSI_COLOR_RESET "" +#else +#define ANSI_COLOR_RED "\x1b[31m" +#define ANSI_COLOR_GREEN "\x1b[32m" +#define ANSI_COLOR_YELLOW "\x1b[33m" +#define ANSI_COLOR_RESET "\x1b[0m" +#endif + +#define VERBOSE_STATUS(status,color,filename) if (verbose) printf( "[ " color " %10s " ANSI_COLOR_RESET " ] %s\n", status, filename->c_str()) + +/// Creates difference images, returns the number that have a 0 metric. +/// If outputDir.isEmpty(), don't write out diff files. +static void create_diff_images (DiffMetricProc dmp, + const int colorThreshold, + RecordArray* differences, + const SkString& baseDir, + const SkString& comparisonDir, + const SkString& outputDir, + const StringArray& matchSubstrings, + const StringArray& nomatchSubstrings, + bool recurseIntoSubdirs, + bool getBounds, + bool verbose, + DiffSummary* summary) { + SkASSERT(!baseDir.isEmpty()); + SkASSERT(!comparisonDir.isEmpty()); + + FileArray baseFiles; + FileArray comparisonFiles; + + get_file_list(baseDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, &baseFiles); + get_file_list(comparisonDir, matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, + &comparisonFiles); + + if (!baseFiles.isEmpty()) { + qsort(baseFiles.begin(), baseFiles.count(), sizeof(SkString*), + SkCastForQSort(compare_file_name_metrics)); + } + if (!comparisonFiles.isEmpty()) { + qsort(comparisonFiles.begin(), comparisonFiles.count(), + sizeof(SkString*), SkCastForQSort(compare_file_name_metrics)); + } + + if (!outputDir.isEmpty()) { + sk_mkdir(outputDir.c_str()); + } + + int i = 0; + int j = 0; + + while (i < baseFiles.count() && + j < comparisonFiles.count()) { + + SkString basePath(baseDir); + SkString comparisonPath(comparisonDir); + + DiffRecord *drp = new DiffRecord; + int v = strcmp(baseFiles[i]->c_str(), comparisonFiles[j]->c_str()); + + if (v < 0) { + // in baseDir, but not in comparisonDir + drp->fResult = DiffRecord::kCouldNotCompare_Result; + + basePath.append(*baseFiles[i]); + comparisonPath.append(*baseFiles[i]); + + drp->fBase.fFilename = *baseFiles[i]; + drp->fBase.fFullPath = basePath; + drp->fBase.fStatus = DiffResource::kExists_Status; + + drp->fComparison.fFilename = *baseFiles[i]; + drp->fComparison.fFullPath = comparisonPath; + drp->fComparison.fStatus = DiffResource::kDoesNotExist_Status; + + VERBOSE_STATUS("MISSING", ANSI_COLOR_YELLOW, baseFiles[i]); + + ++i; + } else if (v > 0) { + // in comparisonDir, but not in baseDir + drp->fResult = DiffRecord::kCouldNotCompare_Result; + + basePath.append(*comparisonFiles[j]); + comparisonPath.append(*comparisonFiles[j]); + + drp->fBase.fFilename = *comparisonFiles[j]; + drp->fBase.fFullPath = basePath; + drp->fBase.fStatus = DiffResource::kDoesNotExist_Status; + + drp->fComparison.fFilename = *comparisonFiles[j]; + drp->fComparison.fFullPath = comparisonPath; + drp->fComparison.fStatus = DiffResource::kExists_Status; + + VERBOSE_STATUS("MISSING", ANSI_COLOR_YELLOW, comparisonFiles[j]); + + ++j; + } else { + // Found the same filename in both baseDir and comparisonDir. + SkASSERT(DiffRecord::kUnknown_Result == drp->fResult); + + basePath.append(*baseFiles[i]); + comparisonPath.append(*comparisonFiles[j]); + + drp->fBase.fFilename = *baseFiles[i]; + drp->fBase.fFullPath = basePath; + drp->fBase.fStatus = DiffResource::kExists_Status; + + drp->fComparison.fFilename = *comparisonFiles[j]; + drp->fComparison.fFullPath = comparisonPath; + drp->fComparison.fStatus = DiffResource::kExists_Status; + + sk_sp<SkData> baseFileBits(read_file(drp->fBase.fFullPath.c_str())); + if (baseFileBits) { + drp->fBase.fStatus = DiffResource::kRead_Status; + } + sk_sp<SkData> comparisonFileBits(read_file(drp->fComparison.fFullPath.c_str())); + if (comparisonFileBits) { + drp->fComparison.fStatus = DiffResource::kRead_Status; + } + if (nullptr == baseFileBits || nullptr == comparisonFileBits) { + if (nullptr == baseFileBits) { + drp->fBase.fStatus = DiffResource::kCouldNotRead_Status; + VERBOSE_STATUS("READ FAIL", ANSI_COLOR_RED, baseFiles[i]); + } + if (nullptr == comparisonFileBits) { + drp->fComparison.fStatus = DiffResource::kCouldNotRead_Status; + VERBOSE_STATUS("READ FAIL", ANSI_COLOR_RED, comparisonFiles[j]); + } + drp->fResult = DiffRecord::kCouldNotCompare_Result; + + } else if (are_buffers_equal(baseFileBits.get(), comparisonFileBits.get())) { + drp->fResult = DiffRecord::kEqualBits_Result; + VERBOSE_STATUS("MATCH", ANSI_COLOR_GREEN, baseFiles[i]); + } else { + AutoReleasePixels arp(drp); + get_bitmap(baseFileBits, drp->fBase, false); + get_bitmap(comparisonFileBits, drp->fComparison, false); + VERBOSE_STATUS("DIFFERENT", ANSI_COLOR_RED, baseFiles[i]); + if (DiffResource::kDecoded_Status == drp->fBase.fStatus && + DiffResource::kDecoded_Status == drp->fComparison.fStatus) { + create_and_write_diff_image(drp, dmp, colorThreshold, + outputDir, drp->fBase.fFilename); + } else { + drp->fResult = DiffRecord::kCouldNotCompare_Result; + } + } + + ++i; + ++j; + } + + if (getBounds) { + get_bounds(*drp); + } + SkASSERT(DiffRecord::kUnknown_Result != drp->fResult); + differences->push(drp); + summary->add(drp); + } + + for (; i < baseFiles.count(); ++i) { + // files only in baseDir + DiffRecord *drp = new DiffRecord(); + drp->fBase.fFilename = *baseFiles[i]; + drp->fBase.fFullPath = baseDir; + drp->fBase.fFullPath.append(drp->fBase.fFilename); + drp->fBase.fStatus = DiffResource::kExists_Status; + + drp->fComparison.fFilename = *baseFiles[i]; + drp->fComparison.fFullPath = comparisonDir; + drp->fComparison.fFullPath.append(drp->fComparison.fFilename); + drp->fComparison.fStatus = DiffResource::kDoesNotExist_Status; + + drp->fResult = DiffRecord::kCouldNotCompare_Result; + if (getBounds) { + get_bounds(*drp); + } + differences->push(drp); + summary->add(drp); + } + + for (; j < comparisonFiles.count(); ++j) { + // files only in comparisonDir + DiffRecord *drp = new DiffRecord(); + drp->fBase.fFilename = *comparisonFiles[j]; + drp->fBase.fFullPath = baseDir; + drp->fBase.fFullPath.append(drp->fBase.fFilename); + drp->fBase.fStatus = DiffResource::kDoesNotExist_Status; + + drp->fComparison.fFilename = *comparisonFiles[j]; + drp->fComparison.fFullPath = comparisonDir; + drp->fComparison.fFullPath.append(drp->fComparison.fFilename); + drp->fComparison.fStatus = DiffResource::kExists_Status; + + drp->fResult = DiffRecord::kCouldNotCompare_Result; + if (getBounds) { + get_bounds(*drp); + } + differences->push(drp); + summary->add(drp); + } + + release_file_list(&baseFiles); + release_file_list(&comparisonFiles); +} + +static void usage (char * argv0) { + SkDebugf("Skia baseline image diff tool\n"); + SkDebugf("\n" +"Usage: \n" +" %s <baseDir> <comparisonDir> [outputDir] \n", argv0); + SkDebugf( +"\nArguments:" +"\n --failonresult <result>: After comparing all file pairs, exit with nonzero" +"\n return code (number of file pairs yielding this" +"\n result) if any file pairs yielded this result." +"\n This flag may be repeated, in which case the" +"\n return code will be the number of fail pairs" +"\n yielding ANY of these results." +"\n --failonstatus <baseStatus> <comparisonStatus>: exit with nonzero return" +"\n code if any file pairs yielded this status." +"\n --help: display this info" +"\n --listfilenames: list all filenames for each result type in stdout" +"\n --match <substring>: compare files whose filenames contain this substring;" +"\n if unspecified, compare ALL files." +"\n this flag may be repeated." +"\n --nodiffs: don't write out image diffs or index.html, just generate" +"\n report on stdout" +"\n --nomatch <substring>: regardless of --match, DO NOT compare files whose" +"\n filenames contain this substring." +"\n this flag may be repeated." +"\n --noprintdirs: do not print the directories used." +"\n --norecurse: do not recurse into subdirectories." +"\n --sortbymaxmismatch: sort by worst color channel mismatch;" +"\n break ties with -sortbymismatch" +"\n --sortbymismatch: sort by average color channel mismatch" +"\n --threshold <n>: only report differences > n (per color channel) [default 0]" +"\n --weighted: sort by # pixels different weighted by color difference" +"\n" +"\n baseDir: directory to read baseline images from." +"\n comparisonDir: directory to read comparison images from" +"\n outputDir: directory to write difference images and index.html to;" +"\n defaults to comparisonDir" +"\n" +"\nIf no sort is specified, it will sort by fraction of pixels mismatching." +"\n"); +} + +const int kNoError = 0; +const int kGenericError = -1; + +int tool_main(int argc, char** argv); +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; +} + +#if !defined SK_BUILD_FOR_IOS +int main(int argc, char * const argv[]) { + return tool_main(argc, (char**) argv); +} +#endif |