aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar bungeman@google.com <bungeman@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>2012-12-05 20:13:12 +0000
committerGravatar bungeman@google.com <bungeman@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>2012-12-05 20:13:12 +0000
commite3c8ddfd03fdf587b4d8400718ae4bb6e9aa8b6d (patch)
treedf186fda7d4b430c3af0dd403c44bec88cc0bd4d
parentb959a9cbd9517e07e7997ea9f42838333d533021 (diff)
Update skdiff.
-rw-r--r--gyp/tools.gyp24
-rw-r--r--tools/skdiff.cpp221
-rw-r--r--tools/skdiff.h265
-rw-r--r--tools/skdiff_html.cpp300
-rw-r--r--tools/skdiff_html.h21
-rw-r--r--tools/skdiff_image.cpp375
-rw-r--r--tools/skdiff_main.cpp1275
-rw-r--r--tools/skdiff_utils.cpp186
-rw-r--r--tools/skdiff_utils.h53
-rwxr-xr-xtools/tests/run.sh2
-rw-r--r--tools/tests/skdiff/identical-bits-or-pixels/output-expected/stdout4
-rw-r--r--tools/tests/skdiff/identical-bits/output-expected/stdout4
-rw-r--r--tools/tests/skdiff/test1/output-expected/index.html20
-rw-r--r--tools/tests/skdiff/test1/output-expected/stdout16
-rw-r--r--tools/tests/skdiff/test2/output-expected/command_line2
-rw-r--r--tools/tests/skdiff/test2/output-expected/stdout10
16 files changed, 1764 insertions, 1014 deletions
diff --git a/gyp/tools.gyp b/gyp/tools.gyp
index e7c22415ae..8a31eaa817 100644
--- a/gyp/tools.gyp
+++ b/gyp/tools.gyp
@@ -28,7 +28,31 @@
'target_name': 'skdiff',
'type': 'executable',
'sources': [
+ '../tools/skdiff.cpp',
+ '../tools/skdiff.h',
+ '../tools/skdiff_html.cpp',
+ '../tools/skdiff_html.h',
'../tools/skdiff_main.cpp',
+ '../tools/skdiff_utils.cpp',
+ '../tools/skdiff_utils.h',
+ ],
+ 'dependencies': [
+ 'skia_base_libs.gyp:skia_base_libs',
+ 'effects.gyp:effects',
+ 'images.gyp:images',
+ ],
+ },
+ {
+ 'target_name': 'skimagediff',
+ 'type': 'executable',
+ 'sources': [
+ '../tools/skdiff.cpp',
+ '../tools/skdiff.h',
+ '../tools/skdiff_html.cpp',
+ '../tools/skdiff_html.h',
+ '../tools/skdiff_image.cpp',
+ '../tools/skdiff_utils.cpp',
+ '../tools/skdiff_utils.h',
],
'dependencies': [
'skia_base_libs.gyp:skia_base_libs',
diff --git a/tools/skdiff.cpp b/tools/skdiff.cpp
new file mode 100644
index 0000000000..a1783a45de
--- /dev/null
+++ b/tools/skdiff.cpp
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2012 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 "SkBitmap.h"
+#include "SkColor.h"
+#include "SkColorPriv.h"
+#include "SkTypes.h"
+
+/*static*/ char const * const DiffRecord::ResultNames[DiffRecord::kResultCount] = {
+ "EqualBits",
+ "EqualPixels",
+ "DifferentPixels",
+ "DifferentSizes",
+ "CouldNotCompare",
+ "Unknown",
+};
+
+DiffRecord::Result DiffRecord::getResultByName(const char *name) {
+ for (int result = 0; result < DiffRecord::kResultCount; ++result) {
+ if (0 == strcmp(DiffRecord::ResultNames[result], name)) {
+ return static_cast<DiffRecord::Result>(result);
+ }
+ }
+ return DiffRecord::kResultCount;
+}
+
+static char const * const ResultDescriptions[DiffRecord::kResultCount] = {
+ "contain exactly the same bits",
+ "contain the same pixel values, but not the same bits",
+ "have identical dimensions but some differing pixels",
+ "have differing dimensions",
+ "could not be compared",
+ "not compared yet",
+};
+
+const char* DiffRecord::getResultDescription(DiffRecord::Result result) {
+ return ResultDescriptions[result];
+}
+
+/*static*/ char const * const DiffResource::StatusNames[DiffResource::kStatusCount] = {
+ "Decoded",
+ "CouldNotDecode",
+
+ "Read",
+ "CouldNotRead",
+
+ "Exists",
+ "DoesNotExist",
+
+ "Specified",
+ "Unspecified",
+
+ "Unknown",
+};
+
+DiffResource::Status DiffResource::getStatusByName(const char *name) {
+ for (int status = 0; status < DiffResource::kStatusCount; ++status) {
+ if (0 == strcmp(DiffResource::StatusNames[status], name)) {
+ return static_cast<DiffResource::Status>(status);
+ }
+ }
+ return DiffResource::kStatusCount;
+}
+
+static char const * const StatusDescriptions[DiffResource::kStatusCount] = {
+ "decoded",
+ "could not be decoded",
+
+ "read",
+ "could not be read",
+
+ "found",
+ "not found",
+
+ "specified",
+ "unspecified",
+
+ "unknown",
+};
+
+const char* DiffResource::getStatusDescription(DiffResource::Status status) {
+ return StatusDescriptions[status];
+}
+
+bool DiffResource::isStatusFailed(DiffResource::Status status) {
+ return DiffResource::kCouldNotDecode_Status == status ||
+ DiffResource::kCouldNotRead_Status == status ||
+ DiffResource::kDoesNotExist_Status == status ||
+ DiffResource::kUnspecified_Status == status ||
+ DiffResource::kUnknown_Status == status;
+}
+
+bool DiffResource::getMatchingStatuses(char* selector, bool statuses[kStatusCount]) {
+ if (!strcmp(selector, "any")) {
+ for (int statusIndex = 0; statusIndex < kStatusCount; ++statusIndex) {
+ statuses[statusIndex] = true;
+ }
+ return true;
+ }
+
+ for (int statusIndex = 0; statusIndex < kStatusCount; ++statusIndex) {
+ statuses[statusIndex] = false;
+ }
+
+ static const char kDelimiterChar = ',';
+ bool understood = true;
+ while (true) {
+ char* delimiterPtr = strchr(selector, kDelimiterChar);
+
+ if (delimiterPtr) {
+ *delimiterPtr = '\0';
+ }
+
+ if (!strcmp(selector, "failed")) {
+ for (int statusIndex = 0; statusIndex < kStatusCount; ++statusIndex) {
+ Status status = static_cast<Status>(statusIndex);
+ statuses[statusIndex] |= isStatusFailed(status);
+ }
+ } else {
+ Status status = getStatusByName(selector);
+ if (status == kStatusCount) {
+ understood = false;
+ } else {
+ statuses[status] = true;
+ }
+ }
+
+ if (!delimiterPtr) {
+ break;
+ }
+
+ *delimiterPtr = kDelimiterChar;
+ selector = delimiterPtr + 1;
+ }
+ return understood;
+}
+
+static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1, const int threshold) {
+ int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
+ int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
+ int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
+ int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
+
+ return ((SkAbs32(da) <= threshold) &&
+ (SkAbs32(dr) <= threshold) &&
+ (SkAbs32(dg) <= threshold) &&
+ (SkAbs32(db) <= threshold));
+}
+
+const SkPMColor PMCOLOR_WHITE = SkPreMultiplyColor(SK_ColorWHITE);
+const SkPMColor PMCOLOR_BLACK = SkPreMultiplyColor(SK_ColorBLACK);
+
+void compute_diff(DiffRecord* dr, DiffMetricProc diffFunction, const int colorThreshold) {
+ const int w = dr->fComparison.fBitmap.width();
+ const int h = dr->fComparison.fBitmap.height();
+ if (w != dr->fBase.fBitmap.width() || h != dr->fBase.fBitmap.height()) {
+ dr->fResult = DiffRecord::kDifferentSizes_Result;
+ return;
+ }
+
+ SkAutoLockPixels alpDiff(dr->fDifference.fBitmap);
+ SkAutoLockPixels alpWhite(dr->fWhite.fBitmap);
+ int mismatchedPixels = 0;
+ int totalMismatchR = 0;
+ int totalMismatchG = 0;
+ int totalMismatchB = 0;
+
+ // Accumulate fractionally different pixels, then divide out
+ // # of pixels at the end.
+ dr->fWeightedFraction = 0;
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ SkPMColor c0 = *dr->fBase.fBitmap.getAddr32(x, y);
+ SkPMColor c1 = *dr->fComparison.fBitmap.getAddr32(x, y);
+ SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
+ SkPMColor outputDifference = diffFunction(c0, c1);
+ uint32_t thisR = SkGetPackedR32(trueDifference);
+ uint32_t thisG = SkGetPackedG32(trueDifference);
+ uint32_t thisB = SkGetPackedB32(trueDifference);
+ totalMismatchR += thisR;
+ totalMismatchG += thisG;
+ totalMismatchB += thisB;
+ // In HSV, value is defined as max RGB component.
+ int value = MAX3(thisR, thisG, thisB);
+ dr->fWeightedFraction += ((float) value) / 255;
+ if (thisR > dr->fMaxMismatchR) {
+ dr->fMaxMismatchR = thisR;
+ }
+ if (thisG > dr->fMaxMismatchG) {
+ dr->fMaxMismatchG = thisG;
+ }
+ if (thisB > dr->fMaxMismatchB) {
+ dr->fMaxMismatchB = thisB;
+ }
+ if (!colors_match_thresholded(c0, c1, colorThreshold)) {
+ mismatchedPixels++;
+ *dr->fDifference.fBitmap.getAddr32(x, y) = outputDifference;
+ *dr->fWhite.fBitmap.getAddr32(x, y) = PMCOLOR_WHITE;
+ } else {
+ *dr->fDifference.fBitmap.getAddr32(x, y) = 0;
+ *dr->fWhite.fBitmap.getAddr32(x, y) = PMCOLOR_BLACK;
+ }
+ }
+ }
+ if (0 == mismatchedPixels) {
+ dr->fResult = DiffRecord::kEqualPixels_Result;
+ return;
+ }
+ dr->fResult = DiffRecord::kDifferentPixels_Result;
+ int pixelCount = w * h;
+ dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
+ dr->fWeightedFraction /= pixelCount;
+ dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
+ dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
+ dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
+}
diff --git a/tools/skdiff.h b/tools/skdiff.h
new file mode 100644
index 0000000000..b9e69ced85
--- /dev/null
+++ b/tools/skdiff.h
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef skdiff_DEFINED
+#define skdiff_DEFINED
+
+#include "SkBitmap.h"
+#include "SkColor.h"
+#include "SkColorPriv.h"
+#include "SkString.h"
+#include "SkTDArray.h"
+
+#if SK_BUILD_FOR_WIN32
+ #define PATH_DIV_STR "\\"
+ #define PATH_DIV_CHAR '\\'
+#else
+ #define PATH_DIV_STR "/"
+ #define PATH_DIV_CHAR '/'
+#endif
+
+#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
+#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
+
+
+struct DiffResource {
+ enum Status {
+ /** The resource was specified, exists, read, and decoded. */
+ kDecoded_Status,
+ /** The resource was specified, exists, read, but could not be decoded. */
+ kCouldNotDecode_Status,
+
+ /** The resource was specified, exists, and read. */
+ kRead_Status,
+ /** The resource was specified, exists, but could not be read. */
+ kCouldNotRead_Status,
+
+ /** The resource was specified and exists. */
+ kExists_Status,
+ /** The resource was specified, but does not exist. */
+ kDoesNotExist_Status,
+
+ /** The resource was specified. */
+ kSpecified_Status,
+ /** The resource was not specified. */
+ kUnspecified_Status,
+
+ /** Nothing is yet known about the resource. */
+ kUnknown_Status,
+
+ /** NOT A VALID VALUE -- used to set up arrays and to represent an unknown value. */
+ kStatusCount
+ };
+ static char const * const StatusNames[DiffResource::kStatusCount];
+
+ /** Returns the Status with this name.
+ * If there is no Status with this name, returns kStatusCount.
+ */
+ static Status getStatusByName(const char *name);
+
+ /** Returns a text description of the given Status type. */
+ static const char *getStatusDescription(Status status);
+
+ /** Returns true if the Status indicates some kind of failure. */
+ static bool isStatusFailed(Status status);
+
+ /** Sets statuses[i] if it is implied by selector, unsets it if not.
+ * Selector may be a comma delimited list of status names, "any", or "failed".
+ * Returns true if the selector was entirely understood, false otherwise.
+ */
+ static bool getMatchingStatuses(char* selector, bool statuses[kStatusCount]);
+
+ DiffResource() : fFilename(), fFullPath(), fBitmap(), fStatus(kUnknown_Status) { };
+
+ /** If isEmpty() indicates no filename available. */
+ SkString fFilename;
+ /** If isEmpty() indicates no path available. */
+ SkString fFullPath;
+ /** If empty() indicates the bitmap could not be created. */
+ SkBitmap fBitmap;
+ Status fStatus;
+};
+
+struct DiffRecord {
+
+ // Result of comparison for each pair of files.
+ // Listed from "better" to "worse", for sorting of results.
+ enum Result {
+ kEqualBits_Result,
+ kEqualPixels_Result,
+ kDifferentPixels_Result,
+ kDifferentSizes_Result,
+ kCouldNotCompare_Result,
+ kUnknown_Result,
+
+ kResultCount // NOT A VALID VALUE--used to set up arrays. Must be last.
+ };
+ static char const * const ResultNames[DiffRecord::kResultCount];
+
+ /** Returns the Result with this name.
+ * If there is no Result with this name, returns kResultCount.
+ */
+ static Result getResultByName(const char *name);
+
+ /** Returns a text description of the given Result type. */
+ static const char *getResultDescription(Result result);
+
+ DiffRecord()
+ : fBase()
+ , fComparison()
+ , fDifference()
+ , fWhite()
+ , fFractionDifference(0)
+ , fWeightedFraction(0)
+ , fAverageMismatchR(0)
+ , fAverageMismatchG(0)
+ , fAverageMismatchB(0)
+ , fMaxMismatchR(0)
+ , fMaxMismatchG(0)
+ , fMaxMismatchB(0)
+ , fResult(kUnknown_Result) {
+ };
+
+ DiffResource fBase;
+ DiffResource fComparison;
+ DiffResource fDifference;
+ DiffResource fWhite;
+
+ /// Arbitrary floating-point metric to be used to sort images from most
+ /// to least different from baseline; values of 0 will be omitted from the
+ /// summary webpage.
+ float fFractionDifference;
+ float fWeightedFraction;
+
+ float fAverageMismatchR;
+ float fAverageMismatchG;
+ float fAverageMismatchB;
+
+ uint32_t fMaxMismatchR;
+ uint32_t fMaxMismatchG;
+ uint32_t fMaxMismatchB;
+
+ /// Which category of diff result.
+ Result fResult;
+};
+
+typedef SkTDArray<DiffRecord*> RecordArray;
+
+/// A wrapper for any sortProc (comparison routine) which applies a first-order
+/// sort beforehand, and a tiebreaker if the sortProc returns 0.
+template<typename T> static int compare(const void* untyped_lhs, const void* untyped_rhs) {
+ const DiffRecord* lhs = *reinterpret_cast<DiffRecord* const *>(untyped_lhs);
+ const DiffRecord* rhs = *reinterpret_cast<DiffRecord* const *>(untyped_rhs);
+
+ // First-order sort... these comparisons should be applied before comparing
+ // pixel values, no matter what.
+ if (lhs->fResult != rhs->fResult) {
+ return (lhs->fResult < rhs->fResult) ? 1 : -1;
+ }
+
+ // Passed first-order sort, so call the pixel comparison routine.
+ int result = T::comparePixels(lhs, rhs);
+ if (result != 0) {
+ return result;
+ }
+
+ // Tiebreaker... if we got to this point, we don't really care
+ // which order they are sorted in, but let's at least be consistent.
+ return strcmp(lhs->fBase.fFilename.c_str(), rhs->fBase.fFilename.c_str());
+}
+
+/// Comparison routine for qsort; sorts by fFractionDifference
+/// from largest to smallest.
+class CompareDiffMetrics {
+public:
+ static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
+ if (lhs->fFractionDifference < rhs->fFractionDifference) {
+ return 1;
+ }
+ if (rhs->fFractionDifference < lhs->fFractionDifference) {
+ return -1;
+ }
+ return 0;
+ }
+};
+
+class CompareDiffWeighted {
+public:
+ static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
+ if (lhs->fWeightedFraction < rhs->fWeightedFraction) {
+ return 1;
+ }
+ if (lhs->fWeightedFraction > rhs->fWeightedFraction) {
+ return -1;
+ }
+ return 0;
+ }
+};
+
+/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
+/// from largest to smallest.
+class CompareDiffMeanMismatches {
+public:
+ static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
+ float leftValue = MAX3(lhs->fAverageMismatchR,
+ lhs->fAverageMismatchG,
+ lhs->fAverageMismatchB);
+ float rightValue = MAX3(rhs->fAverageMismatchR,
+ rhs->fAverageMismatchG,
+ rhs->fAverageMismatchB);
+ if (leftValue < rightValue) {
+ return 1;
+ }
+ if (rightValue < leftValue) {
+ return -1;
+ }
+ return 0;
+ }
+};
+
+/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB})
+/// from largest to smallest.
+class CompareDiffMaxMismatches {
+public:
+ static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
+ uint32_t leftValue = MAX3(lhs->fMaxMismatchR,
+ lhs->fMaxMismatchG,
+ lhs->fMaxMismatchB);
+ uint32_t rightValue = MAX3(rhs->fMaxMismatchR,
+ rhs->fMaxMismatchG,
+ rhs->fMaxMismatchB);
+ if (leftValue < rightValue) {
+ return 1;
+ }
+ if (rightValue < leftValue) {
+ return -1;
+ }
+
+ return CompareDiffMeanMismatches::comparePixels(lhs, rhs);
+ }
+};
+
+
+/// Parameterized routine to compute the color of a pixel in a difference image.
+typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
+
+// from gm
+static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
+ int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
+ int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
+ int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
+
+ return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
+}
+
+/** When finished, dr->fResult should have some value other than kUnknown_Result.
+ * Expects dr->fWhite.fBitmap and dr->fDifference.fBitmap to have the same bounds as
+ * dr->fBase.fBitmap and have a valid pixelref.
+ */
+void compute_diff(DiffRecord* dr, DiffMetricProc diffFunction, const int colorThreshold);
+
+#endif
diff --git a/tools/skdiff_html.cpp b/tools/skdiff_html.cpp
new file mode 100644
index 0000000000..85d8777712
--- /dev/null
+++ b/tools/skdiff_html.cpp
@@ -0,0 +1,300 @@
+/*
+ * Copyright 2012 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 "SkStream.h"
+#include "SkTime.h"
+
+/// Make layout more consistent by scaling image to 240 height, 360 width,
+/// or natural size, whichever is smallest.
+static int compute_image_height(int height, int width) {
+ int retval = 240;
+ if (height < retval) {
+ retval = height;
+ }
+ float scale = (float) retval / height;
+ if (width * scale > 360) {
+ scale = (float) 360 / width;
+ retval = static_cast<int>(height * scale);
+ }
+ return retval;
+}
+
+static void print_table_header(SkFILEWStream* stream,
+ const int matchCount,
+ const int colorThreshold,
+ const RecordArray& differences,
+ const SkString &baseDir,
+ const SkString &comparisonDir,
+ bool doOutputDate = false) {
+ stream->writeText("<table>\n");
+ stream->writeText("<tr><th>");
+ stream->writeText("select image</th>\n<th>");
+ if (doOutputDate) {
+ SkTime::DateTime dt;
+ SkTime::GetDateTime(&dt);
+ stream->writeText("SkDiff run at ");
+ stream->writeDecAsText(dt.fHour);
+ stream->writeText(":");
+ if (dt.fMinute < 10) {
+ stream->writeText("0");
+ }
+ stream->writeDecAsText(dt.fMinute);
+ stream->writeText(":");
+ if (dt.fSecond < 10) {
+ stream->writeText("0");
+ }
+ stream->writeDecAsText(dt.fSecond);
+ stream->writeText("<br>");
+ }
+ stream->writeDecAsText(matchCount);
+ stream->writeText(" of ");
+ stream->writeDecAsText(differences.count());
+ stream->writeText(" diffs matched ");
+ if (colorThreshold == 0) {
+ stream->writeText("exactly");
+ } else {
+ stream->writeText("within ");
+ stream->writeDecAsText(colorThreshold);
+ stream->writeText(" color units per component");
+ }
+ stream->writeText(".<br>");
+ stream->writeText("</th>\n<th>");
+ stream->writeText("every different pixel shown in white");
+ stream->writeText("</th>\n<th>");
+ stream->writeText("color difference at each pixel");
+ stream->writeText("</th>\n<th>baseDir: ");
+ stream->writeText(baseDir.c_str());
+ stream->writeText("</th>\n<th>comparisonDir: ");
+ stream->writeText(comparisonDir.c_str());
+ stream->writeText("</th>\n");
+ stream->writeText("</tr>\n");
+}
+
+static void print_pixel_count(SkFILEWStream* stream, const DiffRecord& diff) {
+ stream->writeText("<br>(");
+ stream->writeDecAsText(static_cast<int>(diff.fFractionDifference *
+ diff.fBase.fBitmap.width() *
+ diff.fBase.fBitmap.height()));
+ stream->writeText(" pixels)");
+/*
+ stream->writeDecAsText(diff.fWeightedFraction *
+ diff.fBaseWidth *
+ diff.fBaseHeight);
+ stream->writeText(" weighted pixels)");
+*/
+}
+
+static void print_checkbox_cell(SkFILEWStream* stream, const DiffRecord& diff) {
+ stream->writeText("<td><input type=\"checkbox\" name=\"");
+ stream->writeText(diff.fBase.fFilename.c_str());
+ stream->writeText("\" checked=\"yes\"></td>");
+}
+
+static void print_label_cell(SkFILEWStream* stream, const DiffRecord& diff) {
+ char metricBuf [20];
+
+ stream->writeText("<td><b>");
+ stream->writeText(diff.fBase.fFilename.c_str());
+ stream->writeText("</b><br>");
+ switch (diff.fResult) {
+ case DiffRecord::kEqualBits_Result:
+ SkDEBUGFAIL("should not encounter DiffRecord with kEqualBits here");
+ return;
+ case DiffRecord::kEqualPixels_Result:
+ SkDEBUGFAIL("should not encounter DiffRecord with kEqualPixels here");
+ return;
+ case DiffRecord::kDifferentSizes_Result:
+ stream->writeText("Image sizes differ</td>");
+ return;
+ case DiffRecord::kDifferentPixels_Result:
+ sprintf(metricBuf, "%.4f%%", 100 * diff.fFractionDifference);
+ stream->writeText(metricBuf);
+ stream->writeText(" of pixels differ");
+ stream->writeText("\n (");
+ sprintf(metricBuf, "%.4f%%", 100 * diff.fWeightedFraction);
+ stream->writeText(metricBuf);
+ stream->writeText(" weighted)");
+ // Write the actual number of pixels that differ if it's < 1%
+ if (diff.fFractionDifference < 0.01) {
+ print_pixel_count(stream, diff);
+ }
+ stream->writeText("<br>Average color mismatch ");
+ stream->writeDecAsText(static_cast<int>(MAX3(diff.fAverageMismatchR,
+ diff.fAverageMismatchG,
+ diff.fAverageMismatchB)));
+ stream->writeText("<br>Max color mismatch ");
+ stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
+ diff.fMaxMismatchG,
+ diff.fMaxMismatchB));
+ stream->writeText("</td>");
+ break;
+ case DiffRecord::kCouldNotCompare_Result:
+ stream->writeText("Could not compare.<br>base: ");
+ stream->writeText(DiffResource::getStatusDescription(diff.fBase.fStatus));
+ stream->writeText("<br>comparison: ");
+ stream->writeText(DiffResource::getStatusDescription(diff.fComparison.fStatus));
+ stream->writeText("</td>");
+ return;
+ default:
+ SkDEBUGFAIL("encountered DiffRecord with unknown result type");
+ return;
+ }
+}
+
+static void print_image_cell(SkFILEWStream* stream, const SkString& path, int height) {
+ stream->writeText("<td><a href=\"");
+ stream->writeText(path.c_str());
+ stream->writeText("\"><img src=\"");
+ stream->writeText(path.c_str());
+ stream->writeText("\" height=\"");
+ stream->writeDecAsText(height);
+ stream->writeText("px\"></a></td>");
+}
+
+static void print_link_cell(SkFILEWStream* stream, const SkString& path, const char* text) {
+ stream->writeText("<td><a href=\"");
+ stream->writeText(path.c_str());
+ stream->writeText("\">");
+ stream->writeText(text);
+ stream->writeText("</a></td>");
+}
+
+static void print_diff_resource_cell(SkFILEWStream* stream, DiffResource& resource,
+ const SkString& relativePath, bool local) {
+ if (resource.fBitmap.empty()) {
+ if (DiffResource::kCouldNotDecode_Status == resource.fStatus) {
+ if (local && !resource.fFilename.isEmpty()) {
+ print_link_cell(stream, resource.fFilename, "N/A");
+ return;
+ }
+ if (!resource.fFullPath.isEmpty()) {
+ if (!resource.fFullPath.startsWith(PATH_DIV_STR)) {
+ resource.fFullPath.prepend(relativePath);
+ }
+ print_link_cell(stream, resource.fFullPath, "N/A");
+ return;
+ }
+ }
+ stream->writeText("<td>N/A</td>");
+ return;
+ }
+
+ int height = compute_image_height(resource.fBitmap.height(), resource.fBitmap.width());
+ if (local) {
+ print_image_cell(stream, resource.fFilename, height);
+ return;
+ }
+ if (!resource.fFullPath.startsWith(PATH_DIV_STR)) {
+ resource.fFullPath.prepend(relativePath);
+ }
+ print_image_cell(stream, resource.fFullPath, height);
+}
+
+static void print_diff_row(SkFILEWStream* stream, DiffRecord& diff, const SkString& relativePath) {
+ stream->writeText("<tr>\n");
+ print_checkbox_cell(stream, diff);
+ print_label_cell(stream, diff);
+ print_diff_resource_cell(stream, diff.fWhite, relativePath, true);
+ print_diff_resource_cell(stream, diff.fDifference, relativePath, true);
+ print_diff_resource_cell(stream, diff.fBase, relativePath, false);
+ print_diff_resource_cell(stream, diff.fComparison, relativePath, false);
+ stream->writeText("</tr>\n");
+ stream->flush();
+}
+
+void print_diff_page(const int matchCount,
+ const int colorThreshold,
+ const RecordArray& differences,
+ const SkString& baseDir,
+ const SkString& comparisonDir,
+ const SkString& outputDir) {
+
+ SkASSERT(!baseDir.isEmpty());
+ SkASSERT(!comparisonDir.isEmpty());
+ SkASSERT(!outputDir.isEmpty());
+
+ SkString outputPath(outputDir);
+ outputPath.append("index.html");
+ //SkFILEWStream outputStream ("index.html");
+ SkFILEWStream outputStream(outputPath.c_str());
+
+ // Need to convert paths from relative-to-cwd to relative-to-outputDir
+ // FIXME this doesn't work if there are '..' inside the outputDir
+
+ bool isPathAbsolute = false;
+ // On Windows or Linux, a path starting with PATH_DIV_CHAR is absolute.
+ if (outputDir.size() > 0 && PATH_DIV_CHAR == outputDir[0]) {
+ isPathAbsolute = true;
+ }
+#ifdef SK_BUILD_FOR_WIN32
+ // On Windows, absolute paths can also start with "x:", where x is any
+ // drive letter.
+ if (outputDir.size() > 1 && ':' == outputDir[1]) {
+ isPathAbsolute = true;
+ }
+#endif
+
+ SkString relativePath;
+ if (!isPathAbsolute) {
+ unsigned int ui;
+ for (ui = 0; ui < outputDir.size(); ui++) {
+ if (outputDir[ui] == PATH_DIV_CHAR) {
+ relativePath.append(".." PATH_DIV_STR);
+ }
+ }
+ }
+
+ outputStream.writeText(
+ "<html>\n<head>\n"
+ "<script src=\"https://ajax.googleapis.com/ajax/"
+ "libs/jquery/1.7.2/jquery.min.js\"></script>\n"
+ "<script type=\"text/javascript\">\n"
+ "function generateCheckedList() {\n"
+ "var boxes = $(\":checkbox:checked\");\n"
+ "var fileCmdLineString = '';\n"
+ "var fileMultiLineString = '';\n"
+ "for (var i = 0; i < boxes.length; i++) {\n"
+ "fileMultiLineString += boxes[i].name + '<br>';\n"
+ "fileCmdLineString += boxes[i].name + '&nbsp;';\n"
+ "}\n"
+ "$(\"#checkedList\").html(fileCmdLineString + "
+ "'<br><br>' + fileMultiLineString);\n"
+ "}\n"
+ "</script>\n</head>\n<body>\n");
+ print_table_header(&outputStream, matchCount, colorThreshold, differences,
+ baseDir, comparisonDir);
+ int i;
+ for (i = 0; i < differences.count(); i++) {
+ DiffRecord* diff = differences[i];
+
+ switch (diff->fResult) {
+ // Cases in which there is no diff to report.
+ case DiffRecord::kEqualBits_Result:
+ case DiffRecord::kEqualPixels_Result:
+ continue;
+ // Cases in which we want a detailed pixel diff.
+ case DiffRecord::kDifferentPixels_Result:
+ case DiffRecord::kDifferentSizes_Result:
+ case DiffRecord::kCouldNotCompare_Result:
+ print_diff_row(&outputStream, *diff, relativePath);
+ continue;
+ default:
+ SkDEBUGFAIL("encountered DiffRecord with unknown result type");
+ continue;
+ }
+ }
+ outputStream.writeText(
+ "</table>\n"
+ "<input type=\"button\" "
+ "onclick=\"generateCheckedList()\" "
+ "value=\"Create Rebaseline List\">\n"
+ "<div id=\"checkedList\"></div>\n"
+ "</body>\n</html>\n");
+ outputStream.flush();
+}
diff --git a/tools/skdiff_html.h b/tools/skdiff_html.h
new file mode 100644
index 0000000000..5e87101f94
--- /dev/null
+++ b/tools/skdiff_html.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef skdiff_html_DEFINED
+#define skdiff_html_DEFINED
+
+#include "skdiff.h"
+class SkString;
+
+void print_diff_page(const int matchCount,
+ const int colorThreshold,
+ const RecordArray& differences,
+ const SkString& baseDir,
+ const SkString& comparisonDir,
+ const SkString& outputDir);
+
+#endif \ No newline at end of file
diff --git a/tools/skdiff_image.cpp b/tools/skdiff_image.cpp
new file mode 100644
index 0000000000..14875874c2
--- /dev/null
+++ b/tools/skdiff_image.cpp
@@ -0,0 +1,375 @@
+/*
+ * Copyright 2012 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_utils.h"
+#include "SkBitmap.h"
+#include "SkData.h"
+#include "SkImageDecoder.h"
+#include "SkImageEncoder.h"
+#include "SkOSFile.h"
+#include "SkTDArray.h"
+#include "SkTemplates.h"
+#include "SkTypes.h"
+
+/// If outputDir.isEmpty(), don't write out diff files.
+static void create_diff_images (DiffMetricProc dmp,
+ const int colorThreshold,
+ const SkString& baseFile,
+ const SkString& comparisonFile,
+ const SkString& outputDir,
+ const SkString& outputFilename,
+ DiffRecord* drp) {
+ SkASSERT(!baseFile.isEmpty());
+ SkASSERT(!comparisonFile.isEmpty());
+
+ drp->fBase.fFilename = baseFile;
+ drp->fBase.fFullPath = baseFile;
+ drp->fBase.fStatus = DiffResource::kSpecified_Status;
+
+ drp->fComparison.fFilename = comparisonFile;
+ drp->fComparison.fFullPath = comparisonFile;
+ drp->fComparison.fStatus = DiffResource::kSpecified_Status;
+
+ SkAutoDataUnref baseFileBits(read_file(drp->fBase.fFullPath.c_str()));
+ if (NULL != baseFileBits) {
+ drp->fBase.fStatus = DiffResource::kRead_Status;
+ }
+ SkAutoDataUnref comparisonFileBits(read_file(drp->fComparison.fFullPath.c_str()));
+ if (NULL != comparisonFileBits) {
+ drp->fComparison.fStatus = DiffResource::kRead_Status;
+ }
+ if (NULL == baseFileBits || NULL == comparisonFileBits) {
+ if (NULL == baseFileBits) {
+ drp->fBase.fStatus = DiffResource::kCouldNotRead_Status;
+ }
+ if (NULL == comparisonFileBits) {
+ drp->fComparison.fStatus = DiffResource::kCouldNotRead_Status;
+ }
+ drp->fResult = DiffRecord::kCouldNotCompare_Result;
+ return;
+ }
+
+ if (are_buffers_equal(baseFileBits, comparisonFileBits)) {
+ drp->fResult = DiffRecord::kEqualBits_Result;
+ return;
+ }
+
+ get_bitmap(baseFileBits, drp->fBase, SkImageDecoder::kDecodePixels_Mode);
+ get_bitmap(comparisonFileBits, drp->fComparison, SkImageDecoder::kDecodePixels_Mode);
+ if (DiffResource::kDecoded_Status != drp->fBase.fStatus ||
+ DiffResource::kDecoded_Status != drp->fComparison.fStatus)
+ {
+ drp->fResult = DiffRecord::kCouldNotCompare_Result;
+ return;
+ }
+
+ create_and_write_diff_image(drp, dmp, colorThreshold, outputDir, outputFilename);
+ //TODO: copy fBase.fFilename and fComparison.fFilename to outputDir
+ // svn and git often present tmp files to diff tools which are promptly deleted
+
+ //TODO: serialize drp to outputDir
+ // write a tool to deserialize them and call print_diff_page
+
+ SkASSERT(DiffRecord::kUnknown_Result != drp->fResult);
+}
+
+static void usage (char * argv0) {
+ SkDebugf("Skia image diff tool\n");
+ SkDebugf("\n"
+"Usage: \n"
+" %s <baseFile> <comparisonFile>\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 yeilded this status."
+"\n --help: display this info"
+"\n --listfilenames: list all filenames for each result type in stdout"
+"\n --nodiffs: don't write out image diffs, just generate report on stdout"
+"\n --outputdir: directory to write difference images"
+"\n --threshold <n>: only report differences > n (per color channel) [default 0]"
+"\n -u: ignored. Recognized for compatibility with svn diff."
+"\n -L: first occurrence label for base, second occurrence label for comparison."
+"\n Labels must be of the form \"<filename>(\t<specifier>)?\"."
+"\n The base <filename> will be used to create files in outputdir."
+"\n"
+"\n baseFile: baseline image file."
+"\n comparisonFile: comparison image file"
+"\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;
+
+ // 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 (NULL == 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;
+}
+
+#if !defined SK_BUILD_FOR_IOS
+int main(int argc, char * const argv[]) {
+ return tool_main(argc, (char**) argv);
+}
+#endif
diff --git a/tools/skdiff_main.cpp b/tools/skdiff_main.cpp
index 3bd14c6cc0..b9b00386e9 100644
--- a/tools/skdiff_main.cpp
+++ b/tools/skdiff_main.cpp
@@ -1,11 +1,13 @@
-
/*
* 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 "SkColorPriv.h"
+#include "skdiff.h"
+#include "skdiff_html.h"
+#include "skdiff_utils.h"
+#include "SkBitmap.h"
#include "SkData.h"
#include "SkImageDecoder.h"
#include "SkImageEncoder.h"
@@ -13,7 +15,6 @@
#include "SkStream.h"
#include "SkTDArray.h"
#include "SkTemplates.h"
-#include "SkTime.h"
#include "SkTSearch.h"
#include "SkTypes.h"
@@ -31,159 +32,25 @@
* Returns zero exit code if all images match across baseDir and comparisonDir.
*/
-#if SK_BUILD_FOR_WIN32
- #define PATH_DIV_STR "\\"
- #define PATH_DIV_CHAR '\\'
-#else
- #define PATH_DIV_STR "/"
- #define PATH_DIV_CHAR '/'
-#endif
-
-// Result of comparison for each pair of files.
-// Listed from "better" to "worse", for sorting of results.
-enum Result {
- kEqualBits,
- kEqualPixels,
- kDifferentPixels,
- kDifferentSizes,
- kDifferentOther,
- kComparisonMissing,
- kBaseMissing,
- kUnknown,
- //
- kNumResultTypes // NOT A VALID VALUE--used to set up arrays. Must be last.
-};
-
-// Returns the Result with this name.
-// If there is no Result with this name, returns kNumResultTypes.
-// TODO: Is there a better return value for the fall-through case?
-static Result getResultByName(const char *name) {
- if (0 == strcmp("EqualBits", name)) {
- return kEqualBits;
- }
- if (0 == strcmp("EqualPixels", name)) {
- return kEqualPixels;
- }
- if (0 == strcmp("DifferentPixels", name)) {
- return kDifferentPixels;
- }
- if (0 == strcmp("DifferentSizes", name)) {
- return kDifferentSizes;
- }
- if (0 == strcmp("DifferentOther", name)) {
- return kDifferentOther;
- }
- if (0 == strcmp("ComparisonMissing", name)) {
- return kComparisonMissing;
- }
- if (0 == strcmp("BaseMissing", name)) {
- return kBaseMissing;
- }
- if (0 == strcmp("Unknown", name)) {
- return kUnknown;
- }
- return kNumResultTypes;
-}
-
-// Returns a text description of the given Result type.
-static const char *getResultDescription(Result result) {
- switch (result) {
- case kEqualBits:
- return "contain exactly the same bits";
- case kEqualPixels:
- return "contain the same pixel values, but not the same bits";
- case kDifferentPixels:
- return "have identical dimensions but some differing pixels";
- case kDifferentSizes:
- return "have differing dimensions";
- case kDifferentOther:
- return "contain different bits and are not parsable images";
- case kBaseMissing:
- return "missing from baseDir";
- case kComparisonMissing:
- return "missing from comparisonDir";
- case kUnknown:
- return "not compared yet";
- default:
- return NULL;
- }
-}
-
-struct DiffRecord {
- DiffRecord (const SkString filename,
- const SkString basePath,
- const SkString comparisonPath,
- const Result result = kUnknown)
- : fFilename (filename)
- , fBasePath (basePath)
- , fComparisonPath (comparisonPath)
- , fBaseBitmap (NULL)
- , fComparisonBitmap (NULL)
- , fDifferenceBitmap (NULL)
- , fWhiteBitmap (NULL)
- , fBaseHeight (0)
- , fBaseWidth (0)
- , fFractionDifference (0)
- , fWeightedFraction (0)
- , fAverageMismatchR (0)
- , fAverageMismatchG (0)
- , fAverageMismatchB (0)
- , fMaxMismatchR (0)
- , fMaxMismatchG (0)
- , fMaxMismatchB (0)
- , fResult(result) {
- };
-
- SkString fFilename;
- SkString fBasePath;
- SkString fComparisonPath;
-
- SkBitmap* fBaseBitmap;
- SkBitmap* fComparisonBitmap;
- SkBitmap* fDifferenceBitmap;
- SkBitmap* fWhiteBitmap;
-
- int fBaseHeight;
- int fBaseWidth;
-
- /// Arbitrary floating-point metric to be used to sort images from most
- /// to least different from baseline; values of 0 will be omitted from the
- /// summary webpage.
- float fFractionDifference;
- float fWeightedFraction;
-
- float fAverageMismatchR;
- float fAverageMismatchG;
- float fAverageMismatchB;
-
- uint32_t fMaxMismatchR;
- uint32_t fMaxMismatchG;
- uint32_t fMaxMismatchB;
-
- /// Which category of diff result.
- Result fResult;
-};
-
-#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
-#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))
-
-const SkPMColor PMCOLOR_WHITE = SkPreMultiplyColor(SK_ColorWHITE);
-const SkPMColor PMCOLOR_BLACK = SkPreMultiplyColor(SK_ColorBLACK);
-
typedef SkTDArray<SkString*> StringArray;
typedef StringArray FileArray;
struct DiffSummary {
DiffSummary ()
- : fNumMatches (0)
- , fNumMismatches (0)
- , fMaxMismatchV (0)
- , fMaxMismatchPercent (0) { };
+ : fNumMatches(0)
+ , fNumMismatches(0)
+ , fMaxMismatchV(0)
+ , fMaxMismatchPercent(0) { };
~DiffSummary() {
- for (int i = 0; i < kNumResultTypes; i++) {
+ 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;
@@ -191,7 +58,48 @@ struct DiffSummary {
uint32_t fMaxMismatchV;
float fMaxMismatchPercent;
- FileArray fResultsOfType[kNumResultTypes];
+ FileArray fResultsOfType[DiffRecord::kResultCount];
+ FileArray fStatusOfType[DiffResource::kStatusCount][DiffResource::kStatusCount];
+
+ 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) {
@@ -206,16 +114,22 @@ struct DiffSummary {
printf("\n");
}
- void print(bool listFilenames, bool failOnResultType[kNumResultTypes]) {
+ 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 < kNumResultTypes; resultInt++) {
- Result result = static_cast<Result>(resultInt);
+ 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], getResultDescription(result), listFilenames);
+ 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);
@@ -228,18 +142,27 @@ struct DiffSummary {
void add (DiffRecord* drp) {
uint32_t mismatchValue;
- fResultsOfType[drp->fResult].push(new SkString(drp->fFilename));
+ 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 kEqualBits:
+ case DiffRecord::kEqualBits_Result:
fNumMatches++;
break;
- case kEqualPixels:
+ case DiffRecord::kEqualPixels_Result:
fNumMatches++;
break;
- case kDifferentSizes:
+ case DiffRecord::kDifferentSizes_Result:
fNumMismatches++;
break;
- case kDifferentPixels:
+ case DiffRecord::kDifferentPixels_Result:
fNumMismatches++;
if (drp->fFractionDifference * 100 > fMaxMismatchPercent) {
fMaxMismatchPercent = drp->fFractionDifference * 100;
@@ -250,16 +173,12 @@ struct DiffSummary {
fMaxMismatchV = mismatchValue;
}
break;
- case kDifferentOther:
- fNumMismatches++;
- break;
- case kBaseMissing:
- fNumMismatches++;
- break;
- case kComparisonMissing:
+ case DiffRecord::kCouldNotCompare_Result:
fNumMismatches++;
+ fStatusOfType[drp->fBase.fStatus][drp->fComparison.fStatus].push(
+ new SkString(drp->fBase.fFilename));
break;
- case kUnknown:
+ case DiffRecord::kUnknown_Result:
SkDEBUGFAIL("adding uncategorized DiffRecord");
break;
default:
@@ -269,449 +188,6 @@ struct DiffSummary {
}
};
-typedef SkTDArray<DiffRecord*> RecordArray;
-
-/// A wrapper for any sortProc (comparison routine) which applies a first-order
-/// sort beforehand, and a tiebreaker if the sortProc returns 0.
-template<typename T>
-static int compare(const void* untyped_lhs, const void* untyped_rhs) {
- const DiffRecord* lhs = *reinterpret_cast<DiffRecord* const*>(untyped_lhs);
- const DiffRecord* rhs = *reinterpret_cast<DiffRecord* const*>(untyped_rhs);
-
- // First-order sort... these comparisons should be applied before comparing
- // pixel values, no matter what.
- if (lhs->fResult != rhs->fResult) {
- return (lhs->fResult < rhs->fResult) ? 1 : -1;
- }
-
- // Passed first-order sort, so call the pixel comparison routine.
- int result = T::comparePixels(lhs, rhs);
- if (result != 0) {
- return result;
- }
-
- // Tiebreaker... if we got to this point, we don't really care
- // which order they are sorted in, but let's at least be consistent.
- return strcmp(lhs->fFilename.c_str(), rhs->fFilename.c_str());
-}
-
-/// Comparison routine for qsort; sorts by fFractionDifference
-/// from largest to smallest.
-class CompareDiffMetrics {
-public:
- static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
- if (lhs->fFractionDifference < rhs->fFractionDifference) {
- return 1;
- }
- if (rhs->fFractionDifference < lhs->fFractionDifference) {
- return -1;
- }
- return 0;
- }
-};
-
-class CompareDiffWeighted {
-public:
- static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
- if (lhs->fWeightedFraction < rhs->fWeightedFraction) {
- return 1;
- }
- if (lhs->fWeightedFraction > rhs->fWeightedFraction) {
- return -1;
- }
- return 0;
- }
-};
-
-/// Comparison routine for qsort; sorts by max(fAverageMismatch{RGB})
-/// from largest to smallest.
-class CompareDiffMeanMismatches {
-public:
- static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
- float leftValue = MAX3(lhs->fAverageMismatchR,
- lhs->fAverageMismatchG,
- lhs->fAverageMismatchB);
- float rightValue = MAX3(rhs->fAverageMismatchR,
- rhs->fAverageMismatchG,
- rhs->fAverageMismatchB);
- if (leftValue < rightValue) {
- return 1;
- }
- if (rightValue < leftValue) {
- return -1;
- }
- return 0;
- }
-};
-
-/// Comparison routine for qsort; sorts by max(fMaxMismatch{RGB})
-/// from largest to smallest.
-class CompareDiffMaxMismatches {
-public:
- static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
- uint32_t leftValue = MAX3(lhs->fMaxMismatchR,
- lhs->fMaxMismatchG,
- lhs->fMaxMismatchB);
- uint32_t rightValue = MAX3(rhs->fMaxMismatchR,
- rhs->fMaxMismatchG,
- rhs->fMaxMismatchB);
- if (leftValue < rightValue) {
- return 1;
- }
- if (rightValue < leftValue) {
- return -1;
- }
-
- return CompareDiffMeanMismatches::comparePixels(lhs, rhs);
- }
-};
-
-
-
-/// Parameterized routine to compute the color of a pixel in a difference image.
-typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);
-
-#if 0 // UNUSED
-static void expand_and_copy (int width, int height, SkBitmap** dest) {
- SkBitmap* temp = new SkBitmap ();
- temp->reset();
- temp->setConfig((*dest)->config(), width, height);
- temp->allocPixels();
- (*dest)->copyPixelsTo(temp->getPixels(), temp->getSize(),
- temp->rowBytes());
- *dest = temp;
-}
-#endif
-
-/// Returns true if the two buffers passed in are both non-NULL, and include
-/// exactly the same byte values (and identical lengths).
-static bool are_buffers_equal(SkData* skdata1, SkData* skdata2) {
- if ((NULL == skdata1) || (NULL == skdata2)) {
- return false;
- }
- if (skdata1->size() != skdata2->size()) {
- return false;
- }
- return (0 == memcmp(skdata1->data(), skdata2->data(), skdata1->size()));
-}
-
-/// Reads the file at the given path and returns its complete contents as an
-/// SkData object (or returns NULL on error).
-static SkData* read_file(const char* file_path) {
- SkFILEStream fileStream(file_path);
- if (!fileStream.isValid()) {
- SkDebugf("WARNING: could not open file <%s> for reading\n", file_path);
- return NULL;
- }
- size_t bytesInFile = fileStream.getLength();
- size_t bytesLeftToRead = bytesInFile;
-
- void* bufferStart = sk_malloc_throw(bytesInFile);
- char* bufferPointer = (char*)bufferStart;
- while (bytesLeftToRead > 0) {
- size_t bytesReadThisTime = fileStream.read(
- bufferPointer, bytesLeftToRead);
- if (0 == bytesReadThisTime) {
- SkDebugf("WARNING: error reading from <%s>\n", file_path);
- sk_free(bufferStart);
- return NULL;
- }
- bytesLeftToRead -= bytesReadThisTime;
- bufferPointer += bytesReadThisTime;
- }
- return SkData::NewFromMalloc(bufferStart, bytesInFile);
-}
-
-/// Decodes binary contents of baseFile and comparisonFile into
-/// diffRecord->fBaseBitmap and diffRecord->fComparisonBitmap.
-/// Returns true if that succeeds.
-static bool get_bitmaps (SkData* baseFileContents,
- SkData* comparisonFileContents,
- DiffRecord* diffRecord) {
- SkMemoryStream compareStream(comparisonFileContents->data(),
- comparisonFileContents->size());
- SkMemoryStream baseStream(baseFileContents->data(),
- baseFileContents->size());
-
- SkImageDecoder* codec = SkImageDecoder::Factory(&baseStream);
- if (NULL == codec) {
- SkDebugf("ERROR: no codec found for basePath <%s>\n",
- diffRecord->fBasePath.c_str());
- return false;
- }
-
- // In debug, the DLL will automatically be unloaded when this is deleted,
- // but that shouldn't be a problem in release mode.
- SkAutoTDelete<SkImageDecoder> ad(codec);
-
- baseStream.rewind();
- if (!codec->decode(&baseStream, diffRecord->fBaseBitmap,
- SkBitmap::kARGB_8888_Config,
- SkImageDecoder::kDecodePixels_Mode)) {
- SkDebugf("ERROR: codec failed for basePath <%s>\n",
- diffRecord->fBasePath.c_str());
- return false;
- }
-
- diffRecord->fBaseWidth = diffRecord->fBaseBitmap->width();
- diffRecord->fBaseHeight = diffRecord->fBaseBitmap->height();
-
- if (!codec->decode(&compareStream, diffRecord->fComparisonBitmap,
- SkBitmap::kARGB_8888_Config,
- SkImageDecoder::kDecodePixels_Mode)) {
- SkDebugf("ERROR: codec failed for comparisonPath <%s>\n",
- diffRecord->fComparisonPath.c_str());
- return false;
- }
-
- return true;
-}
-
-static bool get_bitmap_height_width(const SkString& path,
- int *height, int *width) {
- SkFILEStream stream(path.c_str());
- if (!stream.isValid()) {
- SkDebugf("ERROR: couldn't open file <%s>\n",
- path.c_str());
- return false;
- }
-
- SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
- if (NULL == codec) {
- SkDebugf("ERROR: no codec found for <%s>\n",
- path.c_str());
- return false;
- }
-
- SkAutoTDelete<SkImageDecoder> ad(codec);
- SkBitmap bm;
-
- stream.rewind();
- if (!codec->decode(&stream, &bm,
- SkBitmap::kARGB_8888_Config,
- SkImageDecoder::kDecodePixels_Mode)) {
- SkDebugf("ERROR: codec failed for <%s>\n",
- path.c_str());
- return false;
- }
-
- *height = bm.height();
- *width = bm.width();
-
- return true;
-}
-
-// from gm - thanks to PNG, we need to force all pixels 100% opaque
-static void force_all_opaque(const SkBitmap& bitmap) {
- SkAutoLockPixels lock(bitmap);
- for (int y = 0; y < bitmap.height(); y++) {
- for (int x = 0; x < bitmap.width(); x++) {
- *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
- }
- }
-}
-
-// from gm
-static bool write_bitmap(const SkString& path, const SkBitmap* bitmap) {
- SkBitmap copy;
- bitmap->copyTo(&copy, SkBitmap::kARGB_8888_Config);
- force_all_opaque(copy);
- return SkImageEncoder::EncodeFile(path.c_str(), copy,
- SkImageEncoder::kPNG_Type, 100);
-}
-
-// from gm
-static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
- int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
- int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
- int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
-
- return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
-}
-
-static inline bool colors_match_thresholded(SkPMColor c0, SkPMColor c1,
- const int threshold) {
- int da = SkGetPackedA32(c0) - SkGetPackedA32(c1);
- int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
- int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
- int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
-
- return ((SkAbs32(da) <= threshold) &&
- (SkAbs32(dr) <= threshold) &&
- (SkAbs32(dg) <= threshold) &&
- (SkAbs32(db) <= threshold));
-}
-
-// based on gm
-// Postcondition: when we exit this method, dr->fResult should have some value
-// other than kUnknown.
-static void compute_diff(DiffRecord* dr,
- DiffMetricProc diffFunction,
- const int colorThreshold) {
- SkAutoLockPixels alpDiff(*dr->fDifferenceBitmap);
- SkAutoLockPixels alpWhite(*dr->fWhiteBitmap);
-
- const int w = dr->fComparisonBitmap->width();
- const int h = dr->fComparisonBitmap->height();
- int mismatchedPixels = 0;
- int totalMismatchR = 0;
- int totalMismatchG = 0;
- int totalMismatchB = 0;
-
- if (w != dr->fBaseWidth || h != dr->fBaseHeight) {
- dr->fResult = kDifferentSizes;
- return;
- }
- // Accumulate fractionally different pixels, then divide out
- // # of pixels at the end.
- dr->fWeightedFraction = 0;
- for (int y = 0; y < h; y++) {
- for (int x = 0; x < w; x++) {
- SkPMColor c0 = *dr->fBaseBitmap->getAddr32(x, y);
- SkPMColor c1 = *dr->fComparisonBitmap->getAddr32(x, y);
- SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
- SkPMColor outputDifference = diffFunction(c0, c1);
- uint32_t thisR = SkGetPackedR32(trueDifference);
- uint32_t thisG = SkGetPackedG32(trueDifference);
- uint32_t thisB = SkGetPackedB32(trueDifference);
- totalMismatchR += thisR;
- totalMismatchG += thisG;
- totalMismatchB += thisB;
- // In HSV, value is defined as max RGB component.
- int value = MAX3(thisR, thisG, thisB);
- dr->fWeightedFraction += ((float) value) / 255;
- if (thisR > dr->fMaxMismatchR) {
- dr->fMaxMismatchR = thisR;
- }
- if (thisG > dr->fMaxMismatchG) {
- dr->fMaxMismatchG = thisG;
- }
- if (thisB > dr->fMaxMismatchB) {
- dr->fMaxMismatchB = thisB;
- }
- if (!colors_match_thresholded(c0, c1, colorThreshold)) {
- mismatchedPixels++;
- *dr->fDifferenceBitmap->getAddr32(x, y) = outputDifference;
- *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_WHITE;
- } else {
- *dr->fDifferenceBitmap->getAddr32(x, y) = 0;
- *dr->fWhiteBitmap->getAddr32(x, y) = PMCOLOR_BLACK;
- }
- }
- }
- if (0 == mismatchedPixels) {
- dr->fResult = kEqualPixels;
- return;
- }
- dr->fResult = kDifferentPixels;
- int pixelCount = w * h;
- dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
- dr->fWeightedFraction /= pixelCount;
- dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
- dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
- dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
-}
-
-/// Return a copy of the "input" string, within which we have replaced all instances
-/// of oldSubstring with newSubstring.
-///
-/// TODO: If we like this, we should move it into the core SkString implementation,
-/// adding more checks and ample test cases, and paying more attention to efficiency.
-static SkString replace_all(const SkString &input,
- const char oldSubstring[], const char newSubstring[]) {
- SkString output;
- const char *input_cstr = input.c_str();
- const char *first_char = input_cstr;
- const char *match_char;
- int oldSubstringLen = strlen(oldSubstring);
- while (NULL != (match_char = strstr(first_char, oldSubstring))) {
- output.append(first_char, (match_char - first_char));
- output.append(newSubstring);
- first_char = match_char + oldSubstringLen;
- }
- output.append(first_char);
- return output;
-}
-
-static SkString filename_to_derived_filename (const SkString& filename,
- const char *suffix) {
- SkString diffName (filename);
- const char* cstring = diffName.c_str();
- int dotOffset = strrchr(cstring, '.') - cstring;
- diffName.remove(dotOffset, diffName.size() - dotOffset);
- diffName.append(suffix);
-
- // In case we recursed into subdirectories, replace slashes with something else
- // so the diffs will all be written into a single flat directory.
- diffName = replace_all(diffName, PATH_DIV_STR, "_");
- return diffName;
-}
-
-/// Given a image filename, returns the name of the file containing the
-/// associated difference image.
-static SkString filename_to_diff_filename (const SkString& filename) {
- return filename_to_derived_filename(filename, "-diff.png");
-}
-
-/// Given a image filename, returns the name of the file containing the
-/// "white" difference image.
-static SkString filename_to_white_filename (const SkString& filename) {
- return filename_to_derived_filename(filename, "-white.png");
-}
-
-class AutoCreateAndReleaseBitmaps {
-
-public:
- AutoCreateAndReleaseBitmaps(DiffRecord* drp)
- : fDrp(drp) {
- SkASSERT(drp != NULL);
- drp->fBaseBitmap = SkNEW(SkBitmap);
- drp->fComparisonBitmap = SkNEW(SkBitmap);
- drp->fDifferenceBitmap = SkNEW(SkBitmap);
- drp->fWhiteBitmap = SkNEW(SkBitmap);
- }
- ~AutoCreateAndReleaseBitmaps() {
- SkDELETE(fDrp->fBaseBitmap);
- fDrp->fBaseBitmap = NULL;
- SkDELETE(fDrp->fComparisonBitmap);
- fDrp->fComparisonBitmap = NULL;
- SkDELETE(fDrp->fDifferenceBitmap);
- fDrp->fDifferenceBitmap = NULL;
- SkDELETE(fDrp->fWhiteBitmap);
- fDrp->fWhiteBitmap = NULL;
- }
-
-private:
- DiffRecord* fDrp;
-};
-
-/// If outputDir.isEmpty(), don't write out diff files.
-static void create_and_write_diff_image(DiffRecord* drp,
- DiffMetricProc dmp,
- const int colorThreshold,
- const SkString& outputDir,
- const SkString& filename) {
- const int w = drp->fBaseWidth;
- const int h = drp->fBaseHeight;
- drp->fDifferenceBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
- drp->fDifferenceBitmap->allocPixels();
- drp->fWhiteBitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h);
- drp->fWhiteBitmap->allocPixels();
-
- SkASSERT(kUnknown == drp->fResult);
- compute_diff(drp, dmp, colorThreshold);
- SkASSERT(kUnknown != drp->fResult);
-
- if ((kDifferentPixels == drp->fResult) && !outputDir.isEmpty()) {
- SkString differencePath (outputDir);
- differencePath.append(filename_to_diff_filename(filename));
- write_bitmap(differencePath, drp->fDifferenceBitmap);
- SkString whitePath (outputDir);
- whitePath.append(filename_to_white_filename(filename));
- write_bitmap(whitePath, drp->fWhiteBitmap);
- }
-}
-
/// Returns true if string contains any of these substrings.
static bool string_contains_any_of(const SkString& string,
const StringArray& substrings) {
@@ -798,6 +274,40 @@ 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 != NULL);
+ }
+ ~AutoReleasePixels() {
+ fDrp->fBase.fBitmap.setPixelRef(NULL);
+ fDrp->fComparison.fBitmap.setPixelRef(NULL);
+ fDrp->fDifference.fBitmap.setPixelRef(NULL);
+ fDrp->fWhite.fBitmap.setPixelRef(NULL);
+ }
+
+private:
+ DiffRecord* fDrp;
+};
+
+static void get_bounds(DiffResource& resource, const char* name) {
+ if (resource.fBitmap.empty() && !DiffResource::isStatusFailed(resource.fStatus)) {
+ SkAutoDataUnref fileBits(read_file(resource.fFullPath.c_str()));
+ if (NULL == fileBits) {
+ SkDebugf("WARNING: couldn't read %s file <%s>\n", name, resource.fFullPath.c_str());
+ resource.fStatus = DiffResource::kCouldNotRead_Status;
+ } else {
+ get_bitmap(fileBits, resource, SkImageDecoder::kDecodeBounds_Mode);
+ }
+ }
+}
+
+static void get_bounds(DiffRecord& drp) {
+ get_bounds(drp.fBase, "base");
+ get_bounds(drp.fComparison, "comparison");
+}
+
/// 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,
@@ -809,6 +319,7 @@ static void create_diff_images (DiffMetricProc dmp,
const StringArray& matchSubstrings,
const StringArray& nomatchSubstrings,
bool recurseIntoSubdirs,
+ bool getBounds,
DiffSummary* summary) {
SkASSERT(!baseDir.isEmpty());
SkASSERT(!comparisonDir.isEmpty());
@@ -835,85 +346,143 @@ static void create_diff_images (DiffMetricProc dmp,
while (i < baseFiles.count() &&
j < comparisonFiles.count()) {
- SkString basePath (baseDir);
- basePath.append(*baseFiles[i]);
- SkString comparisonPath (comparisonDir);
- comparisonPath.append(*comparisonFiles[j]);
+ SkString basePath(baseDir);
+ SkString comparisonPath(comparisonDir);
- DiffRecord *drp = NULL;
- int v = strcmp(baseFiles[i]->c_str(),
- comparisonFiles[j]->c_str());
+ 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 = new DiffRecord(*baseFiles[i], basePath, comparisonPath,
- kComparisonMissing);
+ 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;
+
++i;
} else if (v > 0) {
// in comparisonDir, but not in baseDir
- drp = new DiffRecord(*comparisonFiles[j], basePath, comparisonPath,
- kBaseMissing);
+ 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;
+
++j;
} else {
// Found the same filename in both baseDir and comparisonDir.
- drp = new DiffRecord(*baseFiles[i], basePath, comparisonPath);
- SkASSERT(kUnknown == drp->fResult);
-
- SkData* baseFileBits = NULL;
- SkData* comparisonFileBits = NULL;
- if (NULL == (baseFileBits = read_file(basePath.c_str()))) {
- SkDebugf("WARNING: couldn't read base file <%s>\n",
- basePath.c_str());
- drp->fResult = kBaseMissing;
- } else if (NULL == (comparisonFileBits = read_file(comparisonPath.c_str()))) {
- SkDebugf("WARNING: couldn't read comparison file <%s>\n",
- comparisonPath.c_str());
- drp->fResult = kComparisonMissing;
+ 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;
+
+ SkAutoDataUnref baseFileBits(read_file(drp->fBase.fFullPath.c_str()));
+ if (NULL != baseFileBits) {
+ drp->fBase.fStatus = DiffResource::kRead_Status;
+ }
+ SkAutoDataUnref comparisonFileBits(read_file(drp->fComparison.fFullPath.c_str()));
+ if (NULL != comparisonFileBits) {
+ drp->fComparison.fStatus = DiffResource::kRead_Status;
+ }
+ if (NULL == baseFileBits || NULL == comparisonFileBits) {
+ if (NULL == baseFileBits) {
+ drp->fBase.fStatus = DiffResource::kCouldNotRead_Status;
+ }
+ if (NULL == comparisonFileBits) {
+ drp->fComparison.fStatus = DiffResource::kCouldNotRead_Status;
+ }
+ drp->fResult = DiffRecord::kCouldNotCompare_Result;
+
+ } else if (are_buffers_equal(baseFileBits, comparisonFileBits)) {
+ drp->fResult = DiffRecord::kEqualBits_Result;
+
} else {
- if (are_buffers_equal(baseFileBits, comparisonFileBits)) {
- drp->fResult = kEqualBits;
+ AutoReleasePixels arp(drp);
+ get_bitmap(baseFileBits, drp->fBase, SkImageDecoder::kDecodePixels_Mode);
+ get_bitmap(comparisonFileBits, drp->fComparison,
+ SkImageDecoder::kDecodePixels_Mode);
+ 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 {
- AutoCreateAndReleaseBitmaps createBitmaps(drp);
- if (get_bitmaps(baseFileBits, comparisonFileBits, drp)) {
- create_and_write_diff_image(drp, dmp, colorThreshold,
- outputDir, *baseFiles[i]);
- } else {
- drp->fResult = kDifferentOther;
- }
+ drp->fResult = DiffRecord::kCouldNotCompare_Result;
}
}
- if (baseFileBits) {
- baseFileBits->unref();
- }
- if (comparisonFileBits) {
- comparisonFileBits->unref();
- }
+
++i;
++j;
}
- SkASSERT(kUnknown != drp->fResult);
+
+ 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
- SkString basePath (baseDir);
- basePath.append(*baseFiles[i]);
- SkString comparisonPath;
- DiffRecord *drp = new DiffRecord(*baseFiles[i], basePath,
- comparisonPath, kComparisonMissing);
+ 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
- SkString basePath;
- SkString comparisonPath(comparisonDir);
- comparisonPath.append(*comparisonFiles[j]);
- DiffRecord *drp = new DiffRecord(*comparisonFiles[j], basePath,
- comparisonPath, kBaseMissing);
+ 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);
}
@@ -922,331 +491,11 @@ static void create_diff_images (DiffMetricProc dmp,
release_file_list(&comparisonFiles);
}
-/// Make layout more consistent by scaling image to 240 height, 360 width,
-/// or natural size, whichever is smallest.
-static int compute_image_height (int height, int width) {
- int retval = 240;
- if (height < retval) {
- retval = height;
- }
- float scale = (float) retval / height;
- if (width * scale > 360) {
- scale = (float) 360 / width;
- retval = static_cast<int>(height * scale);
- }
- return retval;
-}
-
-static void print_table_header (SkFILEWStream* stream,
- const int matchCount,
- const int colorThreshold,
- const RecordArray& differences,
- const SkString &baseDir,
- const SkString &comparisonDir,
- bool doOutputDate=false) {
- stream->writeText("<table>\n");
- stream->writeText("<tr><th>");
- stream->writeText("select image</th>\n<th>");
- if (doOutputDate) {
- SkTime::DateTime dt;
- SkTime::GetDateTime(&dt);
- stream->writeText("SkDiff run at ");
- stream->writeDecAsText(dt.fHour);
- stream->writeText(":");
- if (dt.fMinute < 10) {
- stream->writeText("0");
- }
- stream->writeDecAsText(dt.fMinute);
- stream->writeText(":");
- if (dt.fSecond < 10) {
- stream->writeText("0");
- }
- stream->writeDecAsText(dt.fSecond);
- stream->writeText("<br>");
- }
- stream->writeDecAsText(matchCount);
- stream->writeText(" of ");
- stream->writeDecAsText(differences.count());
- stream->writeText(" images matched ");
- if (colorThreshold == 0) {
- stream->writeText("exactly");
- } else {
- stream->writeText("within ");
- stream->writeDecAsText(colorThreshold);
- stream->writeText(" color units per component");
- }
- stream->writeText(".<br>");
- stream->writeText("</th>\n<th>");
- stream->writeText("every different pixel shown in white");
- stream->writeText("</th>\n<th>");
- stream->writeText("color difference at each pixel");
- stream->writeText("</th>\n<th>baseDir: ");
- stream->writeText(baseDir.c_str());
- stream->writeText("</th>\n<th>comparisonDir: ");
- stream->writeText(comparisonDir.c_str());
- stream->writeText("</th>\n");
- stream->writeText("</tr>\n");
-}
-
-static void print_pixel_count (SkFILEWStream* stream,
- const DiffRecord& diff) {
- stream->writeText("<br>(");
- stream->writeDecAsText(static_cast<int>(diff.fFractionDifference *
- diff.fBaseWidth *
- diff.fBaseHeight));
- stream->writeText(" pixels)");
-/*
- stream->writeDecAsText(diff.fWeightedFraction *
- diff.fBaseWidth *
- diff.fBaseHeight);
- stream->writeText(" weighted pixels)");
-*/
-}
-
-static void print_checkbox_cell (SkFILEWStream* stream,
- const DiffRecord& diff) {
- stream->writeText("<td><input type=\"checkbox\" name=\"");
- stream->writeText(diff.fFilename.c_str());
- stream->writeText("\" checked=\"yes\"></td>");
-}
-
-static void print_label_cell (SkFILEWStream* stream,
- const DiffRecord& diff) {
- char metricBuf [20];
-
- stream->writeText("<td><b>");
- stream->writeText(diff.fFilename.c_str());
- stream->writeText("</b><br>");
- switch (diff.fResult) {
- case kEqualBits:
- SkDEBUGFAIL("should not encounter DiffRecord with kEqualBits here");
- return;
- case kEqualPixels:
- SkDEBUGFAIL("should not encounter DiffRecord with kEqualPixels here");
- return;
- case kDifferentSizes:
- stream->writeText("Image sizes differ</td>");
- return;
- case kDifferentPixels:
- sprintf(metricBuf, "%12.4f%%", 100 * diff.fFractionDifference);
- stream->writeText(metricBuf);
- stream->writeText(" of pixels differ");
- stream->writeText("\n (");
- sprintf(metricBuf, "%12.4f%%", 100 * diff.fWeightedFraction);
- stream->writeText(metricBuf);
- stream->writeText(" weighted)");
- // Write the actual number of pixels that differ if it's < 1%
- if (diff.fFractionDifference < 0.01) {
- print_pixel_count(stream, diff);
- }
- stream->writeText("<br>Average color mismatch ");
- stream->writeDecAsText(static_cast<int>(MAX3(diff.fAverageMismatchR,
- diff.fAverageMismatchG,
- diff.fAverageMismatchB)));
- stream->writeText("<br>Max color mismatch ");
- stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
- diff.fMaxMismatchG,
- diff.fMaxMismatchB));
- stream->writeText("</td>");
- break;
- case kDifferentOther:
- stream->writeText("Files differ; unable to parse one or both files</td>");
- return;
- case kBaseMissing:
- stream->writeText("Missing from baseDir</td>");
- return;
- case kComparisonMissing:
- stream->writeText("Missing from comparisonDir</td>");
- return;
- default:
- SkDEBUGFAIL("encountered DiffRecord with unknown result type");
- return;
- }
-}
-
-static void print_image_cell (SkFILEWStream* stream,
- const SkString& path,
- int height) {
- stream->writeText("<td><a href=\"");
- stream->writeText(path.c_str());
- stream->writeText("\"><img src=\"");
- stream->writeText(path.c_str());
- stream->writeText("\" height=\"");
- stream->writeDecAsText(height);
- stream->writeText("px\"></a></td>");
-}
-
-#if 0 // UNUSED
-static void print_text_cell (SkFILEWStream* stream, const char* text) {
- stream->writeText("<td align=center>");
- if (NULL != text) {
- stream->writeText(text);
- }
- stream->writeText("</td>");
-}
-#endif
-
-static void print_diff_with_missing_file(SkFILEWStream* stream,
- DiffRecord& diff,
- const SkString& relativePath) {
- stream->writeText("<tr>\n");
- print_checkbox_cell(stream, diff);
- print_label_cell(stream, diff);
- stream->writeText("<td>N/A</td>");
- stream->writeText("<td>N/A</td>");
- if (kBaseMissing != diff.fResult) {
- int h, w;
- if (!get_bitmap_height_width(diff.fBasePath, &h, &w)) {
- stream->writeText("<td>N/A</td>");
- } else {
- int height = compute_image_height(h, w);
- if (!diff.fBasePath.startsWith(PATH_DIV_STR)) {
- diff.fBasePath.prepend(relativePath);
- }
- print_image_cell(stream, diff.fBasePath, height);
- }
- } else {
- stream->writeText("<td>N/A</td>");
- }
- if (kComparisonMissing != diff.fResult) {
- int h, w;
- if (!get_bitmap_height_width(diff.fComparisonPath, &h, &w)) {
- stream->writeText("<td>N/A</td>");
- } else {
- int height = compute_image_height(h, w);
- if (!diff.fComparisonPath.startsWith(PATH_DIV_STR)) {
- diff.fComparisonPath.prepend(relativePath);
- }
- print_image_cell(stream, diff.fComparisonPath, height);
- }
- } else {
- stream->writeText("<td>N/A</td>");
- }
- stream->writeText("</tr>\n");
- stream->flush();
-}
-
-static void print_diff_page (const int matchCount,
- const int colorThreshold,
- const RecordArray& differences,
- const SkString& baseDir,
- const SkString& comparisonDir,
- const SkString& outputDir) {
-
- SkASSERT(!baseDir.isEmpty());
- SkASSERT(!comparisonDir.isEmpty());
- SkASSERT(!outputDir.isEmpty());
-
- SkString outputPath (outputDir);
- outputPath.append("index.html");
- //SkFILEWStream outputStream ("index.html");
- SkFILEWStream outputStream (outputPath.c_str());
-
- // Need to convert paths from relative-to-cwd to relative-to-outputDir
- // FIXME this doesn't work if there are '..' inside the outputDir
-
- bool isPathAbsolute = false;
- // On Windows or Linux, a path starting with PATH_DIV_CHAR is absolute.
- if (outputDir.size() > 0 && PATH_DIV_CHAR == outputDir[0]) {
- isPathAbsolute = true;
- }
-#ifdef SK_BUILD_FOR_WIN32
- // On Windows, absolute paths can also start with "x:", where x is any
- // drive letter.
- if (outputDir.size() > 1 && ':' == outputDir[1]) {
- isPathAbsolute = true;
- }
-#endif
-
- SkString relativePath;
- if (!isPathAbsolute) {
- unsigned int ui;
- for (ui = 0; ui < outputDir.size(); ui++) {
- if (outputDir[ui] == PATH_DIV_CHAR) {
- relativePath.append(".." PATH_DIV_STR);
- }
- }
- }
-
- outputStream.writeText(
- "<html>\n<head>\n"
- "<script src=\"https://ajax.googleapis.com/ajax/"
- "libs/jquery/1.7.2/jquery.min.js\"></script>\n"
- "<script type=\"text/javascript\">\n"
- "function generateCheckedList() {\n"
- "var boxes = $(\":checkbox:checked\");\n"
- "var fileCmdLineString = '';\n"
- "var fileMultiLineString = '';\n"
- "for (var i = 0; i < boxes.length; i++) {\n"
- "fileMultiLineString += boxes[i].name + '<br>';\n"
- "fileCmdLineString += boxes[i].name + '&nbsp;';\n"
- "}\n"
- "$(\"#checkedList\").html(fileCmdLineString + "
- "'<br><br>' + fileMultiLineString);\n"
- "}\n"
- "</script>\n</head>\n<body>\n");
- print_table_header(&outputStream, matchCount, colorThreshold, differences,
- baseDir, comparisonDir);
- int i;
- for (i = 0; i < differences.count(); i++) {
- DiffRecord* diff = differences[i];
-
- switch (diff->fResult) {
- // Cases in which there is no diff to report.
- case kEqualBits:
- case kEqualPixels:
- continue;
- // Cases in which we want a detailed pixel diff.
- case kDifferentPixels:
- break;
- // Cases in which the files differed, but we can't display the diff.
- case kDifferentSizes:
- case kDifferentOther:
- case kBaseMissing:
- case kComparisonMissing:
- print_diff_with_missing_file(&outputStream, *diff, relativePath);
- continue;
- default:
- SkDEBUGFAIL("encountered DiffRecord with unknown result type");
- continue;
- }
-
- if (!diff->fBasePath.startsWith(PATH_DIV_STR)) {
- diff->fBasePath.prepend(relativePath);
- }
- if (!diff->fComparisonPath.startsWith(PATH_DIV_STR)) {
- diff->fComparisonPath.prepend(relativePath);
- }
-
- int height = compute_image_height(diff->fBaseHeight, diff->fBaseWidth);
- outputStream.writeText("<tr>\n");
- print_checkbox_cell(&outputStream, *diff);
- print_label_cell(&outputStream, *diff);
- print_image_cell(&outputStream,
- filename_to_white_filename(diff->fFilename), height);
- print_image_cell(&outputStream,
- filename_to_diff_filename(diff->fFilename), height);
- print_image_cell(&outputStream, diff->fBasePath, height);
- print_image_cell(&outputStream, diff->fComparisonPath, height);
- outputStream.writeText("</tr>\n");
- outputStream.flush();
- }
- outputStream.writeText(
- "</table>\n"
- "<input type=\"button\" "
- "onclick=\"generateCheckedList()\" "
- "value=\"Create Rebaseline List\">\n"
- "<div id=\"checkedList\"></div>\n"
- "</body>\n</html>\n");
- outputStream.flush();
-}
-
static void usage (char * argv0) {
SkDebugf("Skia baseline image diff tool\n");
SkDebugf("\n"
"Usage: \n"
-" %s <baseDir> <comparisonDir> [outputDir] \n"
-, argv0, argv0);
+" %s <baseDir> <comparisonDir> [outputDir] \n", argv0);
SkDebugf(
"\nArguments:"
"\n --failonresult <result>: After comparing all file pairs, exit with nonzero"
@@ -1255,6 +504,8 @@ static void usage (char * argv0) {
"\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;"
@@ -1308,17 +559,59 @@ int tool_main(int argc, char** argv) {
RecordArray differences;
DiffSummary summary;
- bool failOnResultType[kNumResultTypes];
- for (int i = 0; i < kNumResultTypes; i++) {
+ 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")) {
- Result type = getResultByName(argv[++i]);
- failOnResultType[type] = true;
+ 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")) {
@@ -1431,8 +724,9 @@ int tool_main(int argc, char** argv) {
create_diff_images(diffProc, colorThreshold, &differences,
baseDir, comparisonDir, outputDir,
- matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, &summary);
- summary.print(listFilenames, failOnResultType);
+ matchSubstrings, nomatchSubstrings, recurseIntoSubdirs, generateDiffs,
+ &summary);
+ summary.print(listFilenames, failOnResultType, failOnStatusType);
if (differences.count()) {
qsort(differences.begin(), differences.count(),
@@ -1451,11 +745,20 @@ int tool_main(int argc, char** argv) {
nomatchSubstrings.deleteAll();
int num_failing_results = 0;
- for (int i = 0; i < kNumResultTypes; i++) {
+ 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
diff --git a/tools/skdiff_utils.cpp b/tools/skdiff_utils.cpp
new file mode 100644
index 0000000000..0eb405aaa5
--- /dev/null
+++ b/tools/skdiff_utils.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2012 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_utils.h"
+#include "SkBitmap.h"
+#include "SkData.h"
+#include "SkImageDecoder.h"
+#include "SkImageEncoder.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "SkTypes.h"
+
+bool are_buffers_equal(SkData* skdata1, SkData* skdata2) {
+ if ((NULL == skdata1) || (NULL == skdata2)) {
+ return false;
+ }
+ if (skdata1->size() != skdata2->size()) {
+ return false;
+ }
+ return (0 == memcmp(skdata1->data(), skdata2->data(), skdata1->size()));
+}
+
+SkData* read_file(const char* file_path) {
+ SkFILEStream fileStream(file_path);
+ if (!fileStream.isValid()) {
+ SkDebugf("WARNING: could not open file <%s> for reading\n", file_path);
+ return NULL;
+ }
+ size_t bytesInFile = fileStream.getLength();
+ size_t bytesLeftToRead = bytesInFile;
+
+ void* bufferStart = sk_malloc_throw(bytesInFile);
+ char* bufferPointer = (char*)bufferStart;
+ while (bytesLeftToRead > 0) {
+ size_t bytesReadThisTime = fileStream.read(bufferPointer, bytesLeftToRead);
+ if (0 == bytesReadThisTime) {
+ SkDebugf("WARNING: error reading from <%s>\n", file_path);
+ sk_free(bufferStart);
+ return NULL;
+ }
+ bytesLeftToRead -= bytesReadThisTime;
+ bufferPointer += bytesReadThisTime;
+ }
+ return SkData::NewFromMalloc(bufferStart, bytesInFile);
+}
+
+bool get_bitmap(SkData* fileBits, DiffResource& resource, SkImageDecoder::Mode mode) {
+ SkMemoryStream stream(fileBits->data(), fileBits->size());
+
+ SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
+ if (NULL == codec) {
+ SkDebugf("ERROR: no codec found for <%s>\n", resource.fFullPath.c_str());
+ resource.fStatus = DiffResource::kCouldNotDecode_Status;
+ return false;
+ }
+
+ // In debug, the DLL will automatically be unloaded when this is deleted,
+ // but that shouldn't be a problem in release mode.
+ SkAutoTDelete<SkImageDecoder> ad(codec);
+
+ stream.rewind();
+ if (!codec->decode(&stream, &resource.fBitmap, SkBitmap::kARGB_8888_Config, mode)) {
+ SkDebugf("ERROR: codec failed for basePath <%s>\n", resource.fFullPath.c_str());
+ resource.fStatus = DiffResource::kCouldNotDecode_Status;
+ return false;
+ }
+
+ resource.fStatus = DiffResource::kDecoded_Status;
+ return true;
+}
+
+/** Thanks to PNG, we need to force all pixels 100% opaque. */
+static void force_all_opaque(const SkBitmap& bitmap) {
+ SkAutoLockPixels lock(bitmap);
+ for (int y = 0; y < bitmap.height(); y++) {
+ for (int x = 0; x < bitmap.width(); x++) {
+ *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
+ }
+ }
+}
+
+bool write_bitmap(const SkString& path, const SkBitmap& bitmap) {
+ SkBitmap copy;
+ bitmap.copyTo(&copy, SkBitmap::kARGB_8888_Config);
+ force_all_opaque(copy);
+ return SkImageEncoder::EncodeFile(path.c_str(), copy,
+ SkImageEncoder::kPNG_Type, 100);
+}
+
+/// Return a copy of the "input" string, within which we have replaced all instances
+/// of oldSubstring with newSubstring.
+///
+/// TODO: If we like this, we should move it into the core SkString implementation,
+/// adding more checks and ample test cases, and paying more attention to efficiency.
+static SkString replace_all(const SkString &input,
+ const char oldSubstring[], const char newSubstring[]) {
+ SkString output;
+ const char *input_cstr = input.c_str();
+ const char *first_char = input_cstr;
+ const char *match_char;
+ int oldSubstringLen = strlen(oldSubstring);
+ while (NULL != (match_char = strstr(first_char, oldSubstring))) {
+ output.append(first_char, (match_char - first_char));
+ output.append(newSubstring);
+ first_char = match_char + oldSubstringLen;
+ }
+ output.append(first_char);
+ return output;
+}
+
+static SkString filename_to_derived_filename(const SkString& filename, const char *suffix) {
+ SkString diffName (filename);
+ const char* cstring = diffName.c_str();
+ int dotOffset = strrchr(cstring, '.') - cstring;
+ diffName.remove(dotOffset, diffName.size() - dotOffset);
+ diffName.append(suffix);
+
+ // In case we recursed into subdirectories, replace slashes with something else
+ // so the diffs will all be written into a single flat directory.
+ diffName = replace_all(diffName, PATH_DIV_STR, "_");
+ return diffName;
+}
+
+SkString filename_to_diff_filename(const SkString& filename) {
+ return filename_to_derived_filename(filename, "-diff.png");
+}
+
+SkString filename_to_white_filename(const SkString& filename) {
+ return filename_to_derived_filename(filename, "-white.png");
+}
+
+void create_and_write_diff_image(DiffRecord* drp,
+ DiffMetricProc dmp,
+ const int colorThreshold,
+ const SkString& outputDir,
+ const SkString& filename) {
+ const int w = drp->fBase.fBitmap.width();
+ const int h = drp->fBase.fBitmap.height();
+
+ if (w != drp->fComparison.fBitmap.width() || h != drp->fComparison.fBitmap.height()) {
+ drp->fResult = DiffRecord::kDifferentSizes_Result;
+ } else {
+ drp->fDifference.fBitmap.setConfig(SkBitmap::kARGB_8888_Config, w, h);
+ drp->fDifference.fBitmap.allocPixels();
+
+ drp->fWhite.fBitmap.setConfig(SkBitmap::kARGB_8888_Config, w, h);
+ drp->fWhite.fBitmap.allocPixels();
+
+ SkASSERT(DiffRecord::kUnknown_Result == drp->fResult);
+ compute_diff(drp, dmp, colorThreshold);
+ SkASSERT(DiffRecord::kUnknown_Result != drp->fResult);
+ }
+
+ if (outputDir.isEmpty()) {
+ drp->fDifference.fStatus = DiffResource::kUnspecified_Status;
+ drp->fWhite.fStatus = DiffResource::kUnspecified_Status;
+
+ } else {
+ drp->fDifference.fFilename = filename_to_diff_filename(filename);
+ drp->fDifference.fFullPath = outputDir;
+ drp->fDifference.fFullPath.append(drp->fDifference.fFilename);
+ drp->fDifference.fStatus = DiffResource::kSpecified_Status;
+
+ drp->fWhite.fFilename = filename_to_white_filename(filename);
+ drp->fWhite.fFullPath = outputDir;
+ drp->fWhite.fFullPath.append(drp->fWhite.fFilename);
+ drp->fWhite.fStatus = DiffResource::kSpecified_Status;
+
+ if (DiffRecord::kDifferentPixels_Result == drp->fResult) {
+ if (write_bitmap(drp->fDifference.fFullPath, drp->fDifference.fBitmap)) {
+ drp->fDifference.fStatus = DiffResource::kExists_Status;
+ } else {
+ drp->fDifference.fStatus = DiffResource::kDoesNotExist_Status;
+ }
+ if (write_bitmap(drp->fWhite.fFullPath, drp->fWhite.fBitmap)) {
+ drp->fWhite.fStatus = DiffResource::kExists_Status;
+ } else {
+ drp->fWhite.fStatus = DiffResource::kDoesNotExist_Status;
+ }
+ }
+ }
+}
diff --git a/tools/skdiff_utils.h b/tools/skdiff_utils.h
new file mode 100644
index 0000000000..bd496b3388
--- /dev/null
+++ b/tools/skdiff_utils.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef skdiff_utils_DEFINED
+#define skdiff_utils_DEFINED
+
+#include "skdiff.h"
+#include "SkImageDecoder.h"
+
+class SkBitmap;
+class SkData;
+class SkString;
+
+/** Returns true if the two buffers passed in are both non-NULL,
+ * have the same length, and contain exactly the same byte values.
+ */
+bool are_buffers_equal(SkData* skdata1, SkData* skdata2);
+
+/** Reads the file at the given path and returns its complete contents as an
+ * SkData object (or returns NULL on error).
+ */
+SkData* read_file(const char* file_path);
+
+/** Decodes the fileBits into the resource.fBitmap. Returns false on failure. */
+bool get_bitmap(SkData* fileBits, DiffResource& resource, SkImageDecoder::Mode mode);
+
+/** Writes the bitmap as a PNG to the path specified. */
+bool write_bitmap(const SkString& path, const SkBitmap& bitmap);
+
+/** Given an image filename, returns the name of the file containing
+ * the associated difference image.
+ */
+SkString filename_to_diff_filename(const SkString& filename);
+
+/** Given an image filename, returns the name of the file containing
+ * the "white" difference image.
+ */
+SkString filename_to_white_filename(const SkString& filename);
+
+/** Calls compute_diff and handles the difference and white diff resources.
+ * If !outputDir.isEmpty(), writes out difference and white images.
+ */
+void create_and_write_diff_image(DiffRecord* drp,
+ DiffMetricProc dmp,
+ const int colorThreshold,
+ const SkString& outputDir,
+ const SkString& filename);
+
+#endif
diff --git a/tools/tests/run.sh b/tools/tests/run.sh
index 3e3619147f..1acb124017 100755
--- a/tools/tests/run.sh
+++ b/tools/tests/run.sh
@@ -59,7 +59,7 @@ skdiff_test "$SKDIFF_TESTDIR/baseDir $SKDIFF_TESTDIR/comparisonDir" "$SKDIFF_TES
# baseDir or comparisonDir)
# - list filenames with each result type to stdout
# - don't generate HTML output files
-skdiff_test "--failonresult DifferentPixels --failonresult DifferentSizes --failonresult DifferentOther --failonresult Unknown --listfilenames --nodiffs $SKDIFF_TESTDIR/baseDir $SKDIFF_TESTDIR/comparisonDir" "$SKDIFF_TESTDIR/test2"
+skdiff_test "--failonresult DifferentPixels --failonresult DifferentSizes --failonresult Unknown --failonstatus CouldNotDecode,CouldNotRead any --failonstatus any CouldNotDecode,CouldNotRead --listfilenames --nodiffs $SKDIFF_TESTDIR/baseDir $SKDIFF_TESTDIR/comparisonDir" "$SKDIFF_TESTDIR/test2"
# Run skdiff over just the files that have identical bits.
skdiff_test "--nodiffs --match identical-bits $SKDIFF_TESTDIR/baseDir $SKDIFF_TESTDIR/comparisonDir" "$SKDIFF_TESTDIR/identical-bits"
diff --git a/tools/tests/skdiff/identical-bits-or-pixels/output-expected/stdout b/tools/tests/skdiff/identical-bits-or-pixels/output-expected/stdout
index 38575a5769..a0800cff8d 100644
--- a/tools/tests/skdiff/identical-bits-or-pixels/output-expected/stdout
+++ b/tools/tests/skdiff/identical-bits-or-pixels/output-expected/stdout
@@ -7,9 +7,7 @@ compared 3 file pairs:
[_] 1 file pairs contain the same pixel values, but not the same bits
[_] 0 file pairs have identical dimensions but some differing pixels
[_] 0 file pairs have differing dimensions
-[_] 0 file pairs contain different bits and are not parsable images
-[_] 0 file pairs missing from comparisonDir
-[_] 0 file pairs missing from baseDir
+[_] 0 file pairs could not be compared
[_] 0 file pairs not compared yet
(results marked with [*] will cause nonzero return value)
diff --git a/tools/tests/skdiff/identical-bits/output-expected/stdout b/tools/tests/skdiff/identical-bits/output-expected/stdout
index 8e27568b33..d05c1cc709 100644
--- a/tools/tests/skdiff/identical-bits/output-expected/stdout
+++ b/tools/tests/skdiff/identical-bits/output-expected/stdout
@@ -7,9 +7,7 @@ compared 2 file pairs:
[_] 0 file pairs contain the same pixel values, but not the same bits
[_] 0 file pairs have identical dimensions but some differing pixels
[_] 0 file pairs have differing dimensions
-[_] 0 file pairs contain different bits and are not parsable images
-[_] 0 file pairs missing from comparisonDir
-[_] 0 file pairs missing from baseDir
+[_] 0 file pairs could not be compared
[_] 0 file pairs not compared yet
(results marked with [*] will cause nonzero return value)
diff --git a/tools/tests/skdiff/test1/output-expected/index.html b/tools/tests/skdiff/test1/output-expected/index.html
index ed321e243a..a3e192e482 100644
--- a/tools/tests/skdiff/test1/output-expected/index.html
+++ b/tools/tests/skdiff/test1/output-expected/index.html
@@ -17,32 +17,32 @@ $("#checkedList").html(fileCmdLineString + '<br><br>' + fileMultiLineString);
<body>
<table>
<tr><th>select image</th>
-<th>3 of 12 images matched exactly.<br></th>
+<th>3 of 12 diffs matched exactly.<br></th>
<th>every different pixel shown in white</th>
<th>color difference at each pixel</th>
<th>baseDir: tools/tests/skdiff/baseDir/</th>
<th>comparisonDir: tools/tests/skdiff/comparisonDir/</th>
</tr>
<tr>
-<td><input type="checkbox" name="missing-files/missing-from-baseDir.png" checked="yes"></td><td><b>missing-files/missing-from-baseDir.png</b><br>Missing from baseDir</td><td>N/A</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/missing-files/missing-from-baseDir.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/missing-files/missing-from-baseDir.png" height="240px"></a></td></tr>
+<td><input type="checkbox" name="different-bits/different-bits-unknown-format.xyz" checked="yes"></td><td><b>different-bits/different-bits-unknown-format.xyz</b><br>Could not compare.<br>base: could not be decoded<br>comparison: could not be decoded</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/different-bits-unknown-format.xyz">N/A</a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/different-bits-unknown-format.xyz">N/A</a></td></tr>
<tr>
-<td><input type="checkbox" name="missing-files/missing-from-baseDir.xyz" checked="yes"></td><td><b>missing-files/missing-from-baseDir.xyz</b><br>Missing from baseDir</td><td>N/A</td><td>N/A</td><td>N/A</td><td>N/A</td></tr>
+<td><input type="checkbox" name="missing-files/missing-from-baseDir.png" checked="yes"></td><td><b>missing-files/missing-from-baseDir.png</b><br>Could not compare.<br>base: not found<br>comparison: decoded</td><td>N/A</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/missing-files/missing-from-baseDir.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/missing-files/missing-from-baseDir.png" height="240px"></a></td></tr>
<tr>
-<td><input type="checkbox" name="missing-files/missing-from-comparisonDir.png" checked="yes"></td><td><b>missing-files/missing-from-comparisonDir.png</b><br>Missing from comparisonDir</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/baseDir/missing-files/missing-from-comparisonDir.png"><img src="../../../../../tools/tests/skdiff/baseDir/missing-files/missing-from-comparisonDir.png" height="240px"></a></td><td>N/A</td></tr>
+<td><input type="checkbox" name="missing-files/missing-from-baseDir.xyz" checked="yes"></td><td><b>missing-files/missing-from-baseDir.xyz</b><br>Could not compare.<br>base: not found<br>comparison: could not be decoded</td><td>N/A</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/missing-files/missing-from-baseDir.xyz">N/A</a></td></tr>
<tr>
-<td><input type="checkbox" name="missing-files/missing-from-comparisonDir.xyz" checked="yes"></td><td><b>missing-files/missing-from-comparisonDir.xyz</b><br>Missing from comparisonDir</td><td>N/A</td><td>N/A</td><td>N/A</td><td>N/A</td></tr>
+<td><input type="checkbox" name="missing-files/missing-from-comparisonDir.png" checked="yes"></td><td><b>missing-files/missing-from-comparisonDir.png</b><br>Could not compare.<br>base: decoded<br>comparison: not found</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/baseDir/missing-files/missing-from-comparisonDir.png"><img src="../../../../../tools/tests/skdiff/baseDir/missing-files/missing-from-comparisonDir.png" height="240px"></a></td><td>N/A</td></tr>
<tr>
-<td><input type="checkbox" name="different-bits/different-bits-unknown-format.xyz" checked="yes"></td><td><b>different-bits/different-bits-unknown-format.xyz</b><br>Files differ; unable to parse one or both files</td><td>N/A</td><td>N/A</td><td>N/A</td><td>N/A</td></tr>
+<td><input type="checkbox" name="missing-files/missing-from-comparisonDir.xyz" checked="yes"></td><td><b>missing-files/missing-from-comparisonDir.xyz</b><br>Could not compare.<br>base: could not be decoded<br>comparison: not found</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/baseDir/missing-files/missing-from-comparisonDir.xyz">N/A</a></td><td>N/A</td></tr>
<tr>
<td><input type="checkbox" name="different-bits/slightly-different-sizes.png" checked="yes"></td><td><b>different-bits/slightly-different-sizes.png</b><br>Image sizes differ</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/slightly-different-sizes.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/slightly-different-sizes.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/slightly-different-sizes.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/slightly-different-sizes.png" height="240px"></a></td></tr>
<tr>
<td><input type="checkbox" name="different-bits/very-different-sizes.png" checked="yes"></td><td><b>different-bits/very-different-sizes.png</b><br>Image sizes differ</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-sizes.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-sizes.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-sizes.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-sizes.png" height="128px"></a></td></tr>
<tr>
-<td><input type="checkbox" name="different-bits/very-different-pixels-same-size.png" checked="yes"></td><td><b>different-bits/very-different-pixels-same-size.png</b><br> 97.9926% of pixels differ
- ( 42.8911% weighted)<br>Average color mismatch 89<br>Max color mismatch 239</td><td><a href="different-bits_very-different-pixels-same-size-white.png"><img src="different-bits_very-different-pixels-same-size-white.png" height="240px"></a></td><td><a href="different-bits_very-different-pixels-same-size-diff.png"><img src="different-bits_very-different-pixels-same-size-diff.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-pixels-same-size.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-pixels-same-size.png" height="240px"></a></td></tr>
+<td><input type="checkbox" name="different-bits/very-different-pixels-same-size.png" checked="yes"></td><td><b>different-bits/very-different-pixels-same-size.png</b><br>97.9926% of pixels differ
+ (42.8911% weighted)<br>Average color mismatch 89<br>Max color mismatch 239</td><td><a href="different-bits_very-different-pixels-same-size-white.png"><img src="different-bits_very-different-pixels-same-size-white.png" height="240px"></a></td><td><a href="different-bits_very-different-pixels-same-size-diff.png"><img src="different-bits_very-different-pixels-same-size-diff.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-pixels-same-size.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-pixels-same-size.png" height="240px"></a></td></tr>
<tr>
-<td><input type="checkbox" name="different-bits/slightly-different-pixels-same-size.png" checked="yes"></td><td><b>different-bits/slightly-different-pixels-same-size.png</b><br> 0.6630% of pixels differ
- ( 0.1904% weighted)<br>(2164 pixels)<br>Average color mismatch 0<br>Max color mismatch 213</td><td><a href="different-bits_slightly-different-pixels-same-size-white.png"><img src="different-bits_slightly-different-pixels-same-size-white.png" height="240px"></a></td><td><a href="different-bits_slightly-different-pixels-same-size-diff.png"><img src="different-bits_slightly-different-pixels-same-size-diff.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/slightly-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/slightly-different-pixels-same-size.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/slightly-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/slightly-different-pixels-same-size.png" height="240px"></a></td></tr>
+<td><input type="checkbox" name="different-bits/slightly-different-pixels-same-size.png" checked="yes"></td><td><b>different-bits/slightly-different-pixels-same-size.png</b><br>0.6630% of pixels differ
+ (0.1904% weighted)<br>(2164 pixels)<br>Average color mismatch 0<br>Max color mismatch 213</td><td><a href="different-bits_slightly-different-pixels-same-size-white.png"><img src="different-bits_slightly-different-pixels-same-size-white.png" height="240px"></a></td><td><a href="different-bits_slightly-different-pixels-same-size-diff.png"><img src="different-bits_slightly-different-pixels-same-size-diff.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/slightly-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/slightly-different-pixels-same-size.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/slightly-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/slightly-different-pixels-same-size.png" height="240px"></a></td></tr>
</table>
<input type="button" onclick="generateCheckedList()" value="Create Rebaseline List">
<div id="checkedList"></div>
diff --git a/tools/tests/skdiff/test1/output-expected/stdout b/tools/tests/skdiff/test1/output-expected/stdout
index 3208fc4b56..2b8b2d403f 100644
--- a/tools/tests/skdiff/test1/output-expected/stdout
+++ b/tools/tests/skdiff/test1/output-expected/stdout
@@ -1,8 +1,9 @@
-ERROR: no codec found for basePath <tools/tests/skdiff/baseDir/different-bits/different-bits-unknown-format.xyz>
-ERROR: no codec found for <tools/tests/skdiff/comparisonDir/missing-files/missing-from-baseDir.xyz>
-ERROR: no codec found for <tools/tests/skdiff/baseDir/missing-files/missing-from-comparisonDir.xyz>
ERROR: no codec found for <tools/tests/skdiff/baseDir/different-bits/different-bits-unknown-format.xyz>
ERROR: no codec found for <tools/tests/skdiff/comparisonDir/different-bits/different-bits-unknown-format.xyz>
+ERROR: no codec found for <tools/tests/skdiff/baseDir/identical-bits/identical-bits-unknown-format.xyz>
+ERROR: no codec found for <tools/tests/skdiff/comparisonDir/identical-bits/identical-bits-unknown-format.xyz>
+ERROR: no codec found for <tools/tests/skdiff/comparisonDir/missing-files/missing-from-baseDir.xyz>
+ERROR: no codec found for <tools/tests/skdiff/baseDir/missing-files/missing-from-comparisonDir.xyz>
baseDir is [tools/tests/skdiff/baseDir/]
comparisonDir is [tools/tests/skdiff/comparisonDir/]
writing diffs to outputDir is [tools/tests/skdiff/test1/output-actual/]
@@ -12,9 +13,12 @@ compared 12 file pairs:
[_] 1 file pairs contain the same pixel values, but not the same bits
[_] 2 file pairs have identical dimensions but some differing pixels
[_] 2 file pairs have differing dimensions
-[_] 1 file pairs contain different bits and are not parsable images
-[_] 2 file pairs missing from comparisonDir
-[_] 2 file pairs missing from baseDir
+[_] 5 file pairs could not be compared
+ [_] 1 file pairs decoded in baseDir and not found in comparisonDir
+ [_] 1 file pairs could not be decoded in baseDir and could not be decoded in comparisonDir
+ [_] 1 file pairs could not be decoded in baseDir and not found in comparisonDir
+ [_] 1 file pairs not found in baseDir and decoded in comparisonDir
+ [_] 1 file pairs not found in baseDir and could not be decoded in comparisonDir
[_] 0 file pairs not compared yet
(results marked with [*] will cause nonzero return value)
diff --git a/tools/tests/skdiff/test2/output-expected/command_line b/tools/tests/skdiff/test2/output-expected/command_line
index 97724dfbd6..2d8cc5716f 100644
--- a/tools/tests/skdiff/test2/output-expected/command_line
+++ b/tools/tests/skdiff/test2/output-expected/command_line
@@ -1 +1 @@
-out/Debug/skdiff --failonresult DifferentPixels --failonresult DifferentSizes --failonresult DifferentOther --failonresult Unknown --listfilenames --nodiffs tools/tests/skdiff/baseDir tools/tests/skdiff/comparisonDir tools/tests/skdiff/test2/output-actual
+out/Debug/skdiff --failonresult DifferentPixels --failonresult DifferentSizes --failonresult Unknown --failonstatus CouldNotDecode,CouldNotRead any --failonstatus any CouldNotDecode,CouldNotRead --listfilenames --nodiffs tools/tests/skdiff/baseDir tools/tests/skdiff/comparisonDir tools/tests/skdiff/test2/output-actual
diff --git a/tools/tests/skdiff/test2/output-expected/stdout b/tools/tests/skdiff/test2/output-expected/stdout
index ef198e78cf..6aa33b8fd4 100644
--- a/tools/tests/skdiff/test2/output-expected/stdout
+++ b/tools/tests/skdiff/test2/output-expected/stdout
@@ -1,4 +1,5 @@
-ERROR: no codec found for basePath <tools/tests/skdiff/baseDir/different-bits/different-bits-unknown-format.xyz>
+ERROR: no codec found for <tools/tests/skdiff/baseDir/different-bits/different-bits-unknown-format.xyz>
+ERROR: no codec found for <tools/tests/skdiff/comparisonDir/different-bits/different-bits-unknown-format.xyz>
baseDir is [tools/tests/skdiff/baseDir/]
comparisonDir is [tools/tests/skdiff/comparisonDir/]
not writing any diffs to outputDir [tools/tests/skdiff/test2/output-actual/]
@@ -8,9 +9,10 @@ compared 12 file pairs:
[_] 1 file pairs contain the same pixel values, but not the same bits: different-bits/different-bits-identical-pixels.png
[*] 2 file pairs have identical dimensions but some differing pixels: different-bits/slightly-different-pixels-same-size.png different-bits/very-different-pixels-same-size.png
[*] 2 file pairs have differing dimensions: different-bits/slightly-different-sizes.png different-bits/very-different-sizes.png
-[*] 1 file pairs contain different bits and are not parsable images: different-bits/different-bits-unknown-format.xyz
-[_] 2 file pairs missing from comparisonDir: missing-files/missing-from-comparisonDir.png missing-files/missing-from-comparisonDir.xyz
-[_] 2 file pairs missing from baseDir: missing-files/missing-from-baseDir.png missing-files/missing-from-baseDir.xyz
+[_] 5 file pairs could not be compared: different-bits/different-bits-unknown-format.xyz missing-files/missing-from-baseDir.png missing-files/missing-from-baseDir.xyz missing-files/missing-from-comparisonDir.png missing-files/missing-from-comparisonDir.xyz
+ [*] 1 file pairs could not be decoded in baseDir and could not be decoded in comparisonDir: different-bits/different-bits-unknown-format.xyz
+ [_] 2 file pairs found in baseDir and not found in comparisonDir: missing-files/missing-from-comparisonDir.png missing-files/missing-from-comparisonDir.xyz
+ [_] 2 file pairs not found in baseDir and found in comparisonDir: missing-files/missing-from-baseDir.png missing-files/missing-from-baseDir.xyz
[*] 0 file pairs not compared yet:
(results marked with [*] will cause nonzero return value)