Update skdiff.
https://codereview.appspot.com/6850115/


git-svn-id: http://skia.googlecode.com/svn/trunk@6681 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gyp/tools.gyp b/gyp/tools.gyp
index e7c2241..8a31eaa 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 0000000..a1783a4
--- /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 0000000..b9e69ce
--- /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 0000000..85d8777
--- /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 0000000..5e87101
--- /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 0000000..1487587
--- /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 3bd14c6..b9b0038 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 @@
     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 @@
         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 @@
     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 @@
                 fMaxMismatchV = mismatchValue;
             }
             break;
-          case kDifferentOther:
+          case DiffRecord::kCouldNotCompare_Result:
             fNumMismatches++;
+            fStatusOfType[drp->fBase.fStatus][drp->fComparison.fStatus].push(
+                    new SkString(drp->fBase.fFilename));
             break;
-          case kBaseMissing:
-            fNumMismatches++;
-            break;
-          case kComparisonMissing:
-            fNumMismatches++;
-            break;
-          case kUnknown:
+          case DiffRecord::kUnknown_Result:
             SkDEBUGFAIL("adding uncategorized DiffRecord");
             break;
           default:
@@ -269,449 +188,6 @@
     }
 };
 
-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 @@
     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 @@
                                 const StringArray& matchSubstrings,
                                 const StringArray& nomatchSubstrings,
                                 bool recurseIntoSubdirs,
+                                bool getBounds,
                                 DiffSummary* summary) {
     SkASSERT(!baseDir.isEmpty());
     SkASSERT(!comparisonDir.isEmpty());
@@ -835,85 +346,143 @@
     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);
+            SkASSERT(DiffRecord::kUnknown_Result == 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;
+            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 @@
     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 @@
 "\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 @@
     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 @@
 
     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 @@
     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 0000000..0eb405a
--- /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 0000000..bd496b3
--- /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 3e36191..1acb124 100755
--- a/tools/tests/run.sh
+++ b/tools/tests/run.sh
@@ -59,7 +59,7 @@
 #   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 38575a5..a0800cf 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 @@
 [_] 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 8e27568..d05c1cc 100644
--- a/tools/tests/skdiff/identical-bits/output-expected/stdout
+++ b/tools/tests/skdiff/identical-bits/output-expected/stdout
@@ -7,9 +7,7 @@
 [_] 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 ed321e2..a3e192e 100644
--- a/tools/tests/skdiff/test1/output-expected/index.html
+++ b/tools/tests/skdiff/test1/output-expected/index.html
@@ -17,32 +17,32 @@
 <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 3208fc4..2b8b2d4 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 @@
 [_] 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 97724df..2d8cc57 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 ef198e7..6aa33b8 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 @@
 [_] 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)