123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861 |
- /*
- * 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 "include/core/SkBitmap.h"
- #include "include/core/SkData.h"
- #include "include/core/SkImageEncoder.h"
- #include "include/core/SkPixelRef.h"
- #include "include/core/SkStream.h"
- #include "include/private/SkTDArray.h"
- #include "src/core/SkOSFile.h"
- #include "src/core/SkTSearch.h"
- #include "src/utils/SkOSPath.h"
- #include "tools/skdiff/skdiff.h"
- #include "tools/skdiff/skdiff_html.h"
- #include "tools/skdiff/skdiff_utils.h"
- #include <stdlib.h>
- /**
- * 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, SkOSPath::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_back(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_back(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_back(
- 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_back(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, 0, 0);
- fDrp->fComparison.fBitmap.setPixelRef(nullptr, 0, 0);
- fDrp->fDifference.fBitmap.setPixelRef(nullptr, 0, 0);
- fDrp->fWhite.fBitmap.setPixelRef(nullptr, 0, 0);
- }
- 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, 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,
- bool ignoreColorSpace,
- 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, ignoreColorSpace);
- get_bitmap(comparisonFileBits, drp->fComparison, false, ignoreColorSpace);
- 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_back(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_back(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_back(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 --nocolorspace: Ignore color space of images."
- "\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 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;
- bool ignoreColorSpace = 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_back(new SkString(argv[++i]));
- continue;
- }
- if (!strcmp(argv[i], "--nocolorspace")) {
- ignoreColorSpace = true;
- continue;
- }
- if (!strcmp(argv[i], "--nodiffs")) {
- generateDiffs = false;
- continue;
- }
- if (!strcmp(argv[i], "--nomatch")) {
- nomatchSubstrings.push_back(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_back(new SkString(""));
- }
- create_diff_images(diffProc, colorThreshold, ignoreColorSpace, &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;
- }
|