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/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