render_pictures: add --mismatchPath flag

When set, it will only write out images that don't match expectations.

BUG=skia:1942
R=rmistry@google.com

Author: epoger@google.com

Review URL: https://codereview.chromium.org/283123002

git-svn-id: http://skia.googlecode.com/svn/trunk@14748 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/debugger/QT/SkDebuggerGUI.cpp b/debugger/QT/SkDebuggerGUI.cpp
index 66078b0..01d165a 100644
--- a/debugger/QT/SkDebuggerGUI.cpp
+++ b/debugger/QT/SkDebuggerGUI.cpp
@@ -326,7 +326,7 @@
         return;
     }
 
-    renderer->init(pict, NULL, NULL, false);
+    renderer->init(pict, NULL, NULL, NULL, false);
 
     renderer->setup();
     renderer->render();
diff --git a/tools/CopyTilesRenderer.cpp b/tools/CopyTilesRenderer.cpp
index 9e919e0..8022092 100644
--- a/tools/CopyTilesRenderer.cpp
+++ b/tools/CopyTilesRenderer.cpp
@@ -20,15 +20,17 @@
     : fXTilesPerLargeTile(x)
     , fYTilesPerLargeTile(y) {
     }
-    void CopyTilesRenderer::init(SkPicture* pict, const SkString* outputDir,
-                                 const SkString* inputFilename, bool useChecksumBasedFilenames) {
+    void CopyTilesRenderer::init(SkPicture* pict, const SkString* writePath,
+                                 const SkString* mismatchPath, const SkString* inputFilename,
+                                 bool useChecksumBasedFilenames) {
         // Do not call INHERITED::init(), which would create a (potentially large) canvas which is
         // not used by bench_pictures.
         SkASSERT(pict != NULL);
         // Only work with absolute widths (as opposed to percentages).
         SkASSERT(this->getTileWidth() != 0 && this->getTileHeight() != 0);
         fPicture.reset(pict)->ref();
-        this->CopyString(&fOutputDir, outputDir);
+        this->CopyString(&fWritePath, writePath);
+        this->CopyString(&fMismatchPath, mismatchPath);
         this->CopyString(&fInputFilename, inputFilename);
         fUseChecksumBasedFilenames = useChecksumBasedFilenames;
         this->buildBBoxHierarchy();
@@ -64,13 +66,13 @@
                         SkDEBUGCODE(bool extracted =)
                         baseBitmap.extractSubset(&dst, subset);
                         SkASSERT(extracted);
-                        if (!fOutputDir.isEmpty()) {
+                        if (!fWritePath.isEmpty()) {
                             // Similar to write() in PictureRenderer.cpp, but just encodes
                             // a bitmap directly.
                             // TODO: Share more common code with write() to do this, to properly
                             // write out the JSON summary, etc.
                             SkString pathWithNumber;
-                            make_filepath(&pathWithNumber, fOutputDir, fInputFilename);
+                            make_filepath(&pathWithNumber, fWritePath, fInputFilename);
                             pathWithNumber.remove(pathWithNumber.size() - 4, 4);
                             pathWithNumber.appendf("%i.png", i++);
                             SkBitmap copy;
diff --git a/tools/CopyTilesRenderer.h b/tools/CopyTilesRenderer.h
index ef1ccb0..3bf969b 100644
--- a/tools/CopyTilesRenderer.h
+++ b/tools/CopyTilesRenderer.h
@@ -23,7 +23,8 @@
 
     public:
         CopyTilesRenderer(int x, int y);
-        virtual void init(SkPicture* pict, const SkString* outputDir, const SkString* inputFilename,
+        virtual void init(SkPicture* pict, const SkString* writePath, const SkString* mismatchPath,
+                          const SkString* inputFilename,
                           bool useChecksumBasedFilenames) SK_OVERRIDE;
 
         /**
diff --git a/tools/PictureBenchmark.cpp b/tools/PictureBenchmark.cpp
index 2950e7d..6c0325e 100644
--- a/tools/PictureBenchmark.cpp
+++ b/tools/PictureBenchmark.cpp
@@ -74,7 +74,7 @@
         return;
     }
 
-    fRenderer->init(pict, NULL, NULL, false);
+    fRenderer->init(pict, NULL, NULL, NULL, false);
 
     // We throw this away to remove first time effects (such as paging in this program)
     fRenderer->setup();
diff --git a/tools/PictureRenderer.cpp b/tools/PictureRenderer.cpp
index 7456700..12f0afa 100644
--- a/tools/PictureRenderer.cpp
+++ b/tools/PictureRenderer.cpp
@@ -48,9 +48,10 @@
     kDefaultTileHeight = 256
 };
 
-void PictureRenderer::init(SkPicture* pict, const SkString* outputDir,
+void PictureRenderer::init(SkPicture* pict, const SkString* writePath, const SkString* mismatchPath,
                            const SkString* inputFilename, bool useChecksumBasedFilenames) {
-    this->CopyString(&fOutputDir, outputDir);
+    this->CopyString(&fWritePath, writePath);
+    this->CopyString(&fMismatchPath, mismatchPath);
     this->CopyString(&fInputFilename, inputFilename);
     fUseChecksumBasedFilenames = useChecksumBasedFilenames;
 
@@ -265,8 +266,9 @@
  * Write the canvas to an image file and/or JSON summary.
  *
  * @param canvas Must be non-null. Canvas to be written to a file.
- * @param outputDir If nonempty, write the binary image to a file within this directory;
- *     if empty, don't write out the image at all.
+ * @param writePath If nonempty, write the binary image to a file within this directory.
+ * @param mismatchPath If nonempty, write the binary image to a file within this directory,
+ *     but only if the image does not match expectations.
  * @param inputFilename If we are writing out a binary image, use this to build its filename.
  * @param jsonSummaryPtr If not null, add image results (checksum) to this summary.
  * @param useChecksumBasedFilenames If true, use checksum-based filenames when writing to disk.
@@ -274,9 +276,9 @@
  *
  * @return bool True if the operation completed successfully.
  */
-static bool write(SkCanvas* canvas, const SkString& outputDir, const SkString& inputFilename,
-                  ImageResultsAndExpectations *jsonSummaryPtr, bool useChecksumBasedFilenames,
-                  const int* tileNumberPtr=NULL) {
+static bool write(SkCanvas* canvas, const SkString& writePath, const SkString& mismatchPath,
+                  const SkString& inputFilename, ImageResultsAndExpectations *jsonSummaryPtr,
+                  bool useChecksumBasedFilenames, const int* tileNumberPtr=NULL) {
     SkASSERT(canvas != NULL);
     if (NULL == canvas) {
         return false;
@@ -325,21 +327,20 @@
 
         jsonSummaryPtr->add(inputFilename.c_str(), outputRelativePath.c_str(),
                             *imageDigestPtr, tileNumberPtr);
+        if (!mismatchPath.isEmpty() &&
+            !jsonSummaryPtr->matchesExpectation(inputFilename.c_str(), *imageDigestPtr,
+                                                tileNumberPtr)) {
+            if (!write_bitmap_to_disk(bitmap, mismatchPath, outputSubdirPtr, outputFilename)) {
+                return false;
+            }
+        }
     }
 
-    if (outputDir.isEmpty()) {
+    if (writePath.isEmpty()) {
         return true;
-    }
-
-    SkString dirPath;
-    if (outputSubdirPtr) {
-        dirPath = SkOSPath::SkPathJoin(outputDir.c_str(), outputSubdirPtr);
-        sk_mkdir(dirPath.c_str());
     } else {
-        dirPath.set(outputDir);
+        return write_bitmap_to_disk(bitmap, writePath, outputSubdirPtr, outputFilename);
     }
-    SkString fullPath = SkOSPath::SkPathJoin(dirPath.c_str(), outputFilename.c_str());
-    return SkImageEncoder::EncodeFile(fullPath.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -363,9 +364,9 @@
     this->scaleToScaleFactor(canvas);
     fPicture->draw(canvas);
     SkAutoTUnref<SkPicture> picture(recorder.endRecording());
-    if (!fOutputDir.isEmpty()) {
+    if (!fWritePath.isEmpty()) {
         // Record the new picture as a new SKP with PNG encoded bitmaps.
-        SkString skpPath = SkOSPath::SkPathJoin(fOutputDir.c_str(), fInputFilename.c_str());
+        SkString skpPath = SkOSPath::SkPathJoin(fWritePath.c_str(), fInputFilename.c_str());
         SkFILEWStream stream(skpPath.c_str());
         picture->serialize(&stream, &encode_bitmap_to_data);
         return true;
@@ -397,7 +398,7 @@
         setup_bitmap(*out, fPicture->width(), fPicture->height());
         fCanvas->readPixels(*out, 0, 0);
     }
-    return write(fCanvas, fOutputDir, fInputFilename, fJsonSummaryPtr,
+    return write(fCanvas, fWritePath, fMismatchPath, fInputFilename, fJsonSummaryPtr,
                  fUseChecksumBasedFilenames);
 }
 
@@ -407,9 +408,10 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-void SimplePictureRenderer::init(SkPicture* picture, const SkString* outputDir,
-                                 const SkString* inputFilename, bool useChecksumBasedFilenames) {
-    INHERITED::init(picture, outputDir, inputFilename, useChecksumBasedFilenames);
+void SimplePictureRenderer::init(SkPicture* picture, const SkString* writePath,
+                                 const SkString* mismatchPath, const SkString* inputFilename,
+                                 bool useChecksumBasedFilenames) {
+    INHERITED::init(picture, writePath, mismatchPath, inputFilename, useChecksumBasedFilenames);
     this->buildBBoxHierarchy();
 }
 
@@ -427,7 +429,7 @@
         setup_bitmap(*out, fPicture->width(), fPicture->height());
         fCanvas->readPixels(*out, 0, 0);
     }
-    return write(fCanvas, fOutputDir, fInputFilename, fJsonSummaryPtr,
+    return write(fCanvas, fWritePath, fMismatchPath, fInputFilename, fJsonSummaryPtr,
                  fUseChecksumBasedFilenames);
 }
 
@@ -447,8 +449,9 @@
     , fTilesX(0)
     , fTilesY(0) { }
 
-void TiledPictureRenderer::init(SkPicture* pict, const SkString* outputDir,
-                                const SkString* inputFilename, bool useChecksumBasedFilenames) {
+void TiledPictureRenderer::init(SkPicture* pict, const SkString* writePath,
+                                const SkString* mismatchPath, const SkString* inputFilename,
+                                bool useChecksumBasedFilenames) {
     SkASSERT(NULL != pict);
     SkASSERT(0 == fTileRects.count());
     if (NULL == pict || fTileRects.count() != 0) {
@@ -458,7 +461,8 @@
     // Do not call INHERITED::init(), which would create a (potentially large) canvas which is not
     // used by bench_pictures.
     fPicture.reset(pict)->ref();
-    this->CopyString(&fOutputDir, outputDir);
+    this->CopyString(&fWritePath, writePath);
+    this->CopyString(&fMismatchPath, mismatchPath);
     this->CopyString(&fInputFilename, inputFilename);
     fUseChecksumBasedFilenames = useChecksumBasedFilenames;
     this->buildBBoxHierarchy();
@@ -636,7 +640,7 @@
     bool success = true;
     for (int i = 0; i < fTileRects.count(); ++i) {
         draw_tile_to_canvas(fCanvas, fTileRects[i], fPicture);
-        success &= write(fCanvas, fOutputDir, fInputFilename, fJsonSummaryPtr,
+        success &= write(fCanvas, fWritePath, fMismatchPath, fInputFilename, fJsonSummaryPtr,
                          fUseChecksumBasedFilenames, &i);
         if (NULL != out) {
             if (fCanvas->readPixels(&bitmap, 0, 0)) {
@@ -720,7 +724,7 @@
 
         for (int i = fStart; i < fEnd; i++) {
             draw_tile_to_canvas(fCanvas, fRects[i], fClone);
-            if (!write(fCanvas, fOutputDir, fInputFilename, fJsonSummaryPtr,
+            if (!write(fCanvas, fWritePath, fMismatchPath, fInputFilename, fJsonSummaryPtr,
                        fUseChecksumBasedFilenames, &i)
                 && fSuccess != NULL) {
                 *fSuccess = false;
@@ -742,9 +746,10 @@
         fDone->run();
     }
 
-    void setPathsAndSuccess(const SkString& outputDir, const SkString& inputFilename,
-                            bool* success) {
-        fOutputDir.set(outputDir);
+    void setPathsAndSuccess(const SkString& writePath, const SkString& mismatchPath,
+                            const SkString& inputFilename, bool* success) {
+        fWritePath.set(writePath);
+        fMismatchPath.set(mismatchPath);
         fInputFilename.set(inputFilename);
         fSuccess = success;
     }
@@ -758,7 +763,8 @@
     SkPicture*         fClone;      // Picture to draw from. Each CloneData has a unique one which
                                     // is threadsafe.
     SkCanvas*          fCanvas;     // Canvas to draw to. Reused for each tile.
-    SkString           fOutputDir;  // If not empty, write results into this directory.
+    SkString           fWritePath;  // If not empty, write all results into this directory.
+    SkString           fMismatchPath;  // If not empty, write all unexpected results into this dir.
     SkString           fInputFilename; // Filename of input SkPicture file.
     SkTDArray<SkRect>& fRects;      // All tiles of the picture.
     const int          fStart;      // Range of tiles drawn by this thread.
@@ -781,10 +787,11 @@
     fCloneData = SkNEW_ARRAY(CloneData*, fNumThreads);
 }
 
-void MultiCorePictureRenderer::init(SkPicture *pict, const SkString* outputDir,
-                                    const SkString* inputFilename, bool useChecksumBasedFilenames) {
+void MultiCorePictureRenderer::init(SkPicture *pict, const SkString* writePath,
+                                    const SkString* mismatchPath, const SkString* inputFilename,
+                                    bool useChecksumBasedFilenames) {
     // Set fPicture and the tiles.
-    this->INHERITED::init(pict, outputDir, inputFilename, useChecksumBasedFilenames);
+    this->INHERITED::init(pict, writePath, mismatchPath, inputFilename, useChecksumBasedFilenames);
     for (int i = 0; i < fNumThreads; ++i) {
         *fCanvasPool.append() = this->setupCanvas(this->getTileWidth(), this->getTileHeight());
     }
@@ -812,9 +819,9 @@
 
 bool MultiCorePictureRenderer::render(SkBitmap** out) {
     bool success = true;
-    if (!fOutputDir.isEmpty()) {
+    if (!fWritePath.isEmpty() || !fMismatchPath.isEmpty()) {
         for (int i = 0; i < fNumThreads-1; i++) {
-            fCloneData[i]->setPathsAndSuccess(fOutputDir, fInputFilename, &success);
+            fCloneData[i]->setPathsAndSuccess(fWritePath, fMismatchPath, fInputFilename, &success);
         }
     }
 
@@ -912,7 +919,7 @@
         SkData* data = SkPictureUtils::GatherPixelRefs(fPicture, bounds);
         SkSafeUnref(data);
 
-        return (fOutputDir.isEmpty());    // we don't have anything to write
+        return (fWritePath.isEmpty());    // we don't have anything to write
     }
 
 private:
@@ -935,7 +942,7 @@
             SkSafeUnref(clone);
         }
 
-        return (fOutputDir.isEmpty());    // we don't have anything to write
+        return (fWritePath.isEmpty());    // we don't have anything to write
     }
 
 private:
diff --git a/tools/PictureRenderer.h b/tools/PictureRenderer.h
index 342df78..468c567 100644
--- a/tools/PictureRenderer.h
+++ b/tools/PictureRenderer.h
@@ -84,13 +84,15 @@
      * Called with each new SkPicture to render.
      *
      * @param pict The SkPicture to render.
-     * @param outputDir The output directory within which this renderer should write files,
-     *     or NULL if this renderer should not write files at all.
+     * @param writePath The output directory within which this renderer should write all images,
+     *     or NULL if this renderer should not write all images.
+     * @param mismatchPath The output directory within which this renderer should write any images
+     *     which do not match expectations, or NULL if this renderer should not write mismatches.
      * @param inputFilename The name of the input file we are rendering.
      * @param useChecksumBasedFilenames Whether to use checksum-based filenames when writing
      *     bitmap images to disk.
      */
-    virtual void init(SkPicture* pict, const SkString* outputDir,
+    virtual void init(SkPicture* pict, const SkString* writePath, const SkString* mismatchPath,
                       const SkString* inputFilename, bool useChecksumBasedFilenames);
 
     /**
@@ -116,11 +118,13 @@
      * Typically "the work" is rendering an SkPicture into a bitmap, but in some subclasses
      * it is recording the source SkPicture into another SkPicture.
      *
-     * If fOutputDir has been specified, the result of the work will be written to that dir.
+     * If fWritePath has been specified, the result of the work will be written to that dir.
+     * If fMismatchPath has been specified, and the actual image result differs from its
+     * expectation, the result of the work will be written to that dir.
      *
      * @param out If non-null, the implementing subclass MAY allocate an SkBitmap, copy the
      *            output image into it, and return it here.  (Some subclasses ignore this parameter)
-     * @return bool True if rendering succeeded and, if fOutputDir had been specified, the output
+     * @return bool True if rendering succeeded and, if fWritePath had been specified, the output
      *              was successfully written to a file.
      */
     virtual bool render(SkBitmap** out = NULL) = 0;
@@ -370,7 +374,8 @@
     BBoxHierarchyType      fBBoxHierarchyType;
     DrawFilterFlags        fDrawFilters[SkDrawFilter::kTypeCount];
     SkString               fDrawFiltersConfig;
-    SkString               fOutputDir;
+    SkString               fWritePath;
+    SkString               fMismatchPath;
     SkString               fInputFilename;
     SkTileGridFactory::TileGridInfo fGridInfo; // used when fBBoxHierarchyType is TileGrid
 
@@ -447,7 +452,7 @@
 
 class SimplePictureRenderer : public PictureRenderer {
 public:
-    virtual void init(SkPicture* pict, const SkString* outputDir,
+    virtual void init(SkPicture* pict, const SkString* writePath, const SkString* mismatchPath,
                       const SkString* inputFilename, bool useChecksumBasedFilenames) SK_OVERRIDE;
 
     virtual bool render(SkBitmap** out = NULL) SK_OVERRIDE;
@@ -462,12 +467,12 @@
 public:
     TiledPictureRenderer();
 
-    virtual void init(SkPicture* pict, const SkString* outputDir,
+    virtual void init(SkPicture* pict, const SkString* writePath, const SkString* mismatchPath,
                       const SkString* inputFilename, bool useChecksumBasedFilenames) SK_OVERRIDE;
 
     /**
      * Renders to tiles, rather than a single canvas.
-     * If fOutputDir was provided, a separate file is
+     * If fWritePath was provided, a separate file is
      * created for each tile, named "path0.png", "path1.png", etc.
      * Multithreaded mode currently does not support writing to a file.
      */
@@ -587,7 +592,7 @@
 
     ~MultiCorePictureRenderer();
 
-    virtual void init(SkPicture* pict, const SkString* outputDir,
+    virtual void init(SkPicture* pict, const SkString* writePath, const SkString* mismatchPath,
                       const SkString* inputFilename, bool useChecksumBasedFilenames) SK_OVERRIDE;
 
     /**
diff --git a/tools/bbh_shootout.cpp b/tools/bbh_shootout.cpp
index 65d3782..f3758cb 100644
--- a/tools/bbh_shootout.cpp
+++ b/tools/bbh_shootout.cpp
@@ -67,7 +67,7 @@
         BenchTimer* timer) {
     renderer->setBBoxHierarchyType(bBoxType);
     renderer->setGridSize(FLAGS_tilesize, FLAGS_tilesize);
-    renderer->init(pic, NULL, NULL, false);
+    renderer->init(pic, NULL, NULL, NULL, false);
 
     SkDebugf("%s %d times...\n", renderer->getConfigName().c_str(), numRepeats);
     for (int i = 0; i < numRepeats; ++i) {
diff --git a/tools/image_expectations.cpp b/tools/image_expectations.cpp
index 9b180da..24c9175 100644
--- a/tools/image_expectations.cpp
+++ b/tools/image_expectations.cpp
@@ -104,7 +104,7 @@
     }
 
     void ImageResultsAndExpectations::add(const char *sourceName, const char *fileName,
-                                  const ImageDigest &digest, const int *tileNumber) {
+                                          const ImageDigest &digest, const int *tileNumber) {
         // Get expectation, if any.
         Json::Value expectedImage;
         if (!fExpectedResults.isNull()) {
@@ -146,6 +146,29 @@
         }
     }
 
+    bool ImageResultsAndExpectations::matchesExpectation(const char *sourceName,
+                                                         const ImageDigest &digest,
+                                                         const int *tileNumber) {
+        if (fExpectedResults.isNull()) {
+            return false;
+        }
+
+        Json::Value expectedImage;
+        if (NULL == tileNumber) {
+            expectedImage = fExpectedResults[sourceName][kJsonKey_Source_WholeImage];
+        } else {
+            expectedImage = fExpectedResults[sourceName][kJsonKey_Source_TiledImages][*tileNumber];
+        }
+        if (expectedImage.isNull()) {
+            return false;
+        }
+
+        Json::Value actualChecksumAlgorithm = digest.getHashType().c_str();
+        Json::Value actualChecksumValue = Json::UInt64(digest.getHashValue());
+        return ((actualChecksumAlgorithm == expectedImage[kJsonKey_Image_ChecksumAlgorithm]) &&
+                (actualChecksumValue == expectedImage[kJsonKey_Image_ChecksumValue]));
+    }
+
     void ImageResultsAndExpectations::writeToFile(const char *filename) const {
         Json::Value header;
         header[kJsonKey_Header_Type] = kJsonValue_Header_Type;
diff --git a/tools/image_expectations.h b/tools/image_expectations.h
index 09a945a..b7b135d 100644
--- a/tools/image_expectations.h
+++ b/tools/image_expectations.h
@@ -90,12 +90,23 @@
          * @param sourceName name of the source file that generated this result
          * @param fileName relative path to the image output file on local disk
          * @param digest description of the image's contents
-         * @param tileNumber if not NULL, ptr to tile number
+         * @param tileNumber if not NULL, pointer to tile number
          */
         void add(const char *sourceName, const char *fileName, const ImageDigest &digest,
                  const int *tileNumber=NULL);
 
         /**
+         * Returns true if this test result matches its expectations.
+         * If there are no expectations for this test result, this will return false.
+         *
+         * @param sourceName name of the source file that generated this result
+         * @param digest description of the image's contents
+         * @param tileNumber if not NULL, pointer to tile number
+         */
+        bool matchesExpectation(const char *sourceName, const ImageDigest &digest,
+                                const int *tileNumber=NULL);
+
+        /**
          * Writes the summary (as constructed so far) to a file.
          *
          * @param filename path to write the summary to
diff --git a/tools/picture_utils.cpp b/tools/picture_utils.cpp
index 4bcdf8a..c698a69 100644
--- a/tools/picture_utils.cpp
+++ b/tools/picture_utils.cpp
@@ -6,11 +6,13 @@
  */
 
 #include "picture_utils.h"
-#include "SkColorPriv.h"
 #include "SkBitmap.h"
+#include "SkColorPriv.h"
+#include "SkImageEncoder.h"
+#include "SkOSFile.h"
 #include "SkPicture.h"
-#include "SkString.h"
 #include "SkStream.h"
+#include "SkString.h"
 
 static bool is_path_seperator(const char chr) {
 #if defined(SK_BUILD_FOR_WIN)
@@ -99,4 +101,23 @@
         bitmap->allocPixels();
         bitmap->eraseColor(SK_ColorTRANSPARENT);
     }
-}
+
+    bool write_bitmap_to_disk(const SkBitmap& bm, const SkString& dirPath,
+                              const char *subdirOrNull, const SkString& baseName) {
+        SkString partialPath;
+        if (subdirOrNull) {
+            partialPath = SkOSPath::SkPathJoin(dirPath.c_str(), subdirOrNull);
+            sk_mkdir(partialPath.c_str());
+        } else {
+            partialPath.set(dirPath);
+        }
+        SkString fullPath = SkOSPath::SkPathJoin(partialPath.c_str(), baseName.c_str());
+        if (SkImageEncoder::EncodeFile(fullPath.c_str(), bm, SkImageEncoder::kPNG_Type, 100)) {
+            return true;
+        } else {
+            SkDebugf("Failed to write the bitmap to %s.\n", fullPath.c_str());
+            return false;
+        }
+    }
+
+} // namespace sk_tools
diff --git a/tools/picture_utils.h b/tools/picture_utils.h
index 02eee28..c0e0d2c 100644
--- a/tools/picture_utils.h
+++ b/tools/picture_utils.h
@@ -53,6 +53,20 @@
     // Specifically, it configures the bitmap, allocates pixels and then
     // erases the pixels to transparent black.
     void setup_bitmap(SkBitmap* bitmap, int width, int height);
-}
+
+    /**
+     * Write a bitmap file to disk.
+     *
+     * @param bm the bitmap to record
+     * @param dirPath directory within which to write the image file
+     * @param subdirOrNull subdirectory within dirPath, or NULL to just write into dirPath
+     * @param baseName last part of the filename
+     *
+     * @return true if written out successfully
+     */
+    bool write_bitmap_to_disk(const SkBitmap& bm, const SkString& dirPath,
+                              const char *subdirOrNull, const SkString& baseName);
+
+} // namespace sk_tools
 
 #endif  // picture_utils_DEFINED
diff --git a/tools/render_pictures_main.cpp b/tools/render_pictures_main.cpp
index 9f28bff..cbbca7a 100644
--- a/tools/render_pictures_main.cpp
+++ b/tools/render_pictures_main.cpp
@@ -31,6 +31,8 @@
 DEFINE_int32(maxComponentDiff, 256, "Maximum diff on a component, 0 - 256. Components that differ "
              "by more than this amount are considered errors, though all diffs are reported. "
              "Requires --validate.");
+DEFINE_string(mismatchPath, "", "Write images for tests that failed due to "
+              "pixel mismatches into this directory.");
 DEFINE_string(readJsonSummaryPath, "", "JSON file to read image expectations from.");
 DECLARE_string(readPath);
 DEFINE_bool(writeChecksumBasedFilenames, false,
@@ -137,14 +139,19 @@
 /**
  * Called only by render_picture().
  */
-static bool render_picture_internal(const SkString& inputPath, const SkString* outputDir,
+static bool render_picture_internal(const SkString& inputPath, const SkString* writePath,
+                                    const SkString* mismatchPath,
                                     sk_tools::PictureRenderer& renderer,
                                     SkBitmap** out) {
     SkString inputFilename;
     sk_tools::get_basename(&inputFilename, inputPath);
-    SkString outputDirString;
-    if (NULL != outputDir && outputDir->size() > 0 && !FLAGS_writeEncodedImages) {
-        outputDirString.set(*outputDir);
+    SkString writePathString;
+    if (NULL != writePath && writePath->size() > 0 && !FLAGS_writeEncodedImages) {
+        writePathString.set(*writePath);
+    }
+    SkString mismatchPathString;
+    if (NULL != mismatchPath && mismatchPath->size() > 0) {
+        mismatchPathString.set(*mismatchPath);
     }
 
     SkFILEStream inputStream;
@@ -189,7 +196,8 @@
     SkDebugf("drawing... [%i %i] %s\n", picture->width(), picture->height(),
              inputPath.c_str());
 
-    renderer.init(picture, &outputDirString, &inputFilename, FLAGS_writeChecksumBasedFilenames);
+    renderer.init(picture, &writePathString, &mismatchPathString, &inputFilename,
+                  FLAGS_writeChecksumBasedFilenames);
 
     if (FLAGS_preprocess) {
         if (NULL != renderer.getCanvas()) {
@@ -246,21 +254,23 @@
 };
 
 /**
- * Render the SKP file(s) within inputPath, writing their bitmap images into outputDir.
+ * Render the SKP file(s) within inputPath.
  *
  * @param inputPath path to an individual SKP file, or a directory of SKP files
- * @param outputDir if not NULL, write the image(s) generated into this directory
+ * @param writePath if not NULL, write all image(s) generated into this directory
+ * @param mismatchPath if not NULL, write any image(s) not matching expectations into this directory
  * @param renderer PictureRenderer to use to render the SKPs
  * @param jsonSummaryPtr if not NULL, add the image(s) generated to this summary
  */
-static bool render_picture(const SkString& inputPath, const SkString* outputDir,
-                           sk_tools::PictureRenderer& renderer,
+static bool render_picture(const SkString& inputPath, const SkString* writePath,
+                           const SkString* mismatchPath, sk_tools::PictureRenderer& renderer,
                            sk_tools::ImageResultsAndExpectations *jsonSummaryPtr) {
     int diffs[256] = {0};
     SkBitmap* bitmap = NULL;
     renderer.setJsonSummaryPtr(jsonSummaryPtr);
     bool success = render_picture_internal(inputPath,
-        FLAGS_writeWholeImage ? NULL : outputDir,
+        FLAGS_writeWholeImage ? NULL : writePath,
+        FLAGS_writeWholeImage ? NULL : mismatchPath,
         renderer,
         FLAGS_validate || FLAGS_writeWholeImage ? &bitmap : NULL);
 
@@ -286,7 +296,7 @@
         }
         SkAutoTUnref<sk_tools::PictureRenderer> aurReferenceRenderer(referenceRenderer);
 
-        success = render_picture_internal(inputPath, NULL, *referenceRenderer,
+        success = render_picture_internal(inputPath, NULL, NULL, *referenceRenderer,
                                           &referenceBitmap);
 
         if (!success || NULL == referenceBitmap || NULL == referenceBitmap->getPixels()) {
@@ -342,25 +352,24 @@
     if (FLAGS_writeWholeImage) {
         sk_tools::force_all_opaque(*bitmap);
 
-        SkString inputFilename, outputPath;
+        SkString inputFilename;
         sk_tools::get_basename(&inputFilename, inputPath);
-        sk_tools::make_filepath(&outputPath, *outputDir, inputFilename);
-        sk_tools::replace_char(&outputPath, '.', '_');
-        outputPath.append(".png");
+        SkString outputFilename(inputFilename);
+        sk_tools::replace_char(&outputFilename, '.', '_');
+        outputFilename.append(".png");
 
         if (NULL != jsonSummaryPtr) {
             sk_tools::ImageDigest imageDigest(*bitmap);
-            SkString outputFileBasename;
-            sk_tools::get_basename(&outputFileBasename, outputPath);
-            jsonSummaryPtr->add(inputFilename.c_str(), outputFileBasename.c_str(), imageDigest);
+            jsonSummaryPtr->add(inputFilename.c_str(), outputFilename.c_str(), imageDigest);
+            if ((NULL != mismatchPath) && !mismatchPath->isEmpty() &&
+                !jsonSummaryPtr->matchesExpectation(inputFilename.c_str(), imageDigest)) {
+                success &= sk_tools::write_bitmap_to_disk(*bitmap, *mismatchPath, NULL,
+                                                          outputFilename);
+            }
         }
 
-        if (NULL != outputDir) {
-            if (!SkImageEncoder::EncodeFile(outputPath.c_str(), *bitmap,
-                                            SkImageEncoder::kPNG_Type, 100)) {
-                SkDebugf("Failed to draw the picture.\n");
-                success = false;
-            }
+        if ((NULL != writePath) && !writePath->isEmpty()) {
+            success &= sk_tools::write_bitmap_to_disk(*bitmap, *writePath, NULL, outputFilename);
         }
     }
     SkDELETE(bitmap);
@@ -369,8 +378,8 @@
 }
 
 
-static int process_input(const char* input, const SkString* outputDir,
-                         sk_tools::PictureRenderer& renderer,
+static int process_input(const char* input, const SkString* writePath,
+                         const SkString* mismatchPath, sk_tools::PictureRenderer& renderer,
                          sk_tools::ImageResultsAndExpectations *jsonSummaryPtr) {
     SkOSFile::Iter iter(input, "skp");
     SkString inputFilename;
@@ -381,13 +390,13 @@
             SkString inputPath;
             SkString inputAsSkString(input);
             sk_tools::make_filepath(&inputPath, inputAsSkString, inputFilename);
-            if (!render_picture(inputPath, outputDir, renderer, jsonSummaryPtr)) {
+            if (!render_picture(inputPath, writePath, mismatchPath, renderer, jsonSummaryPtr)) {
                 ++failures;
             }
         } while(iter.next(&inputFilename));
     } else if (SkStrEndsWith(input, ".skp")) {
         SkString inputPath(input);
-        if (!render_picture(inputPath, outputDir, renderer, jsonSummaryPtr)) {
+        if (!render_picture(inputPath, writePath, mismatchPath, renderer, jsonSummaryPtr)) {
             ++failures;
         }
     } else {
@@ -447,9 +456,13 @@
 
     SkAutoGraphics ag;
 
-    SkString outputDir;
+    SkString writePath;
     if (FLAGS_writePath.count() == 1) {
-        outputDir.set(FLAGS_writePath[0]);
+        writePath.set(FLAGS_writePath[0]);
+    }
+    SkString mismatchPath;
+    if (FLAGS_mismatchPath.count() == 1) {
+        mismatchPath.set(FLAGS_mismatchPath[0]);
     }
     sk_tools::ImageResultsAndExpectations jsonSummary;
     sk_tools::ImageResultsAndExpectations* jsonSummaryPtr = NULL;
@@ -462,7 +475,8 @@
 
     int failures = 0;
     for (int i = 0; i < FLAGS_readPath.count(); i ++) {
-        failures += process_input(FLAGS_readPath[i], &outputDir, *renderer.get(), jsonSummaryPtr);
+        failures += process_input(FLAGS_readPath[i], &writePath, &mismatchPath, *renderer.get(),
+                                  jsonSummaryPtr);
     }
     if (failures != 0) {
         SkDebugf("Failed to render %i pictures.\n", failures);
diff --git a/tools/tests/base_unittest.py b/tools/tests/base_unittest.py
index 2adaed0..f7ee570 100755
--- a/tools/tests/base_unittest.py
+++ b/tools/tests/base_unittest.py
@@ -10,7 +10,9 @@
 for various unittests within this directory.
 """
 
+import errno
 import os
+import shutil
 import sys
 import unittest
 
@@ -26,6 +28,20 @@
     """Tell unittest framework to not print docstrings for test cases."""
     return None
 
+  def create_empty_dir(self, path):
+    """Creates an empty directory at path and returns path.
+
+    Args:
+      path: path on local disk
+    """
+    shutil.rmtree(path=path, ignore_errors=True)
+    try:
+      os.makedirs(path)
+    except OSError as exc:
+      if exc.errno != errno.EEXIST:
+        raise
+    return path
+
   def run_command(self, args):
     """Runs a program from the command line and returns stdout.
 
diff --git a/tools/tests/render_pictures_test.py b/tools/tests/render_pictures_test.py
index 4b11e56..5ab9d67 100755
--- a/tools/tests/render_pictures_test.py
+++ b/tools/tests/render_pictures_test.py
@@ -156,12 +156,13 @@
     self.maxDiff = MAX_DIFF_LENGTH
     self._expectations_dir = tempfile.mkdtemp()
     self._input_skp_dir = tempfile.mkdtemp()
-    self._temp_dir = tempfile.mkdtemp()
+    # All output of render_pictures binary will go into this directory.
+    self._output_dir = tempfile.mkdtemp()
 
   def tearDown(self):
     shutil.rmtree(self._expectations_dir)
     shutil.rmtree(self._input_skp_dir)
-    shutil.rmtree(self._temp_dir)
+    shutil.rmtree(self._output_dir)
 
   def test_tiled_whole_image(self):
     """Run render_pictures with tiles and --writeWholeImage flag.
@@ -169,6 +170,7 @@
     TODO(epoger): This test generates undesired results!  The JSON summary
     includes both whole-image and tiled-images (as it should), but only
     whole-images are written out to disk.  See http://skbug.com/2463
+    Once I fix that, I should add a similar test that exercises mismatchPath.
 
     TODO(epoger): I noticed that when this is run without --writePath being
     specified, this test writes red_skp.png and green_skp.png to the current
@@ -176,7 +178,9 @@
     probably shouldn't write out red_skp.png and green_skp.png at all!
     See http://skbug.com/2464
     """
-    output_json_path = os.path.join(self._temp_dir, 'actuals.json')
+    output_json_path = os.path.join(self._output_dir, 'actuals.json')
+    write_path_dir = self.create_empty_dir(
+        path=os.path.join(self._output_dir, 'writePath'))
     self._generate_skps()
     expectations_path = self._create_expectations()
     self._run_render_pictures([
@@ -185,7 +189,7 @@
         '--mode', 'tile', '256', '256',
         '--readJsonSummaryPath', expectations_path,
         '--writeJsonSummaryPath', output_json_path,
-        '--writePath', self._temp_dir,
+        '--writePath', write_path_dir,
         '--writeWholeImage'])
     expected_summary_dict = {
         "header" : EXPECTED_HEADER_CONTENTS,
@@ -202,12 +206,14 @@
     }
     self._assert_json_contents(output_json_path, expected_summary_dict)
     self._assert_directory_contents(
-        self._temp_dir, ['red_skp.png', 'green_skp.png', 'actuals.json'])
+        write_path_dir, ['red_skp.png', 'green_skp.png'])
 
   def test_missing_tile_and_whole_image(self):
     """test_tiled_whole_image, but missing expectations for some images.
     """
-    output_json_path = os.path.join(self._temp_dir, 'actuals.json')
+    output_json_path = os.path.join(self._output_dir, 'actuals.json')
+    write_path_dir = self.create_empty_dir(
+        path=os.path.join(self._output_dir, 'writePath'))
     self._generate_skps()
     expectations_path = self._create_expectations(missing_some_images=True)
     self._run_render_pictures([
@@ -216,7 +222,7 @@
         '--mode', 'tile', '256', '256',
         '--readJsonSummaryPath', expectations_path,
         '--writeJsonSummaryPath', output_json_path,
-        '--writePath', self._temp_dir,
+        '--writePath', write_path_dir,
         '--writeWholeImage'])
     modified_red_tiles = copy.deepcopy(RED_TILES)
     modified_red_tiles[5]['comparisonResult'] = 'no-comparison'
@@ -238,13 +244,15 @@
 
   def test_untiled(self):
     """Run without tiles."""
-    output_json_path = os.path.join(self._temp_dir, 'actuals.json')
+    output_json_path = os.path.join(self._output_dir, 'actuals.json')
+    write_path_dir = self.create_empty_dir(
+        path=os.path.join(self._output_dir, 'writePath'))
     self._generate_skps()
     expectations_path = self._create_expectations()
     self._run_render_pictures([
         '-r', self._input_skp_dir,
         '--readJsonSummaryPath', expectations_path,
-        '--writePath', self._temp_dir,
+        '--writePath', write_path_dir,
         '--writeJsonSummaryPath', output_json_path])
     expected_summary_dict = {
         "header" : EXPECTED_HEADER_CONTENTS,
@@ -259,15 +267,17 @@
     }
     self._assert_json_contents(output_json_path, expected_summary_dict)
     self._assert_directory_contents(
-        self._temp_dir, ['red_skp.png', 'green_skp.png', 'actuals.json'])
+        write_path_dir, ['red_skp.png', 'green_skp.png'])
 
   def test_untiled_writeChecksumBasedFilenames(self):
     """Same as test_untiled, but with --writeChecksumBasedFilenames."""
-    output_json_path = os.path.join(self._temp_dir, 'actuals.json')
+    output_json_path = os.path.join(self._output_dir, 'actuals.json')
+    write_path_dir = self.create_empty_dir(
+        path=os.path.join(self._output_dir, 'writePath'))
     self._generate_skps()
     self._run_render_pictures(['-r', self._input_skp_dir,
                                '--writeChecksumBasedFilenames',
-                               '--writePath', self._temp_dir,
+                               '--writePath', write_path_dir,
                                '--writeJsonSummaryPath', output_json_path])
     expected_summary_dict = {
         "header" : EXPECTED_HEADER_CONTENTS,
@@ -293,25 +303,26 @@
         }
     }
     self._assert_json_contents(output_json_path, expected_summary_dict)
-    self._assert_directory_contents(self._temp_dir, [
-        'red_skp', 'green_skp', 'actuals.json'])
+    self._assert_directory_contents(write_path_dir, ['red_skp', 'green_skp'])
     self._assert_directory_contents(
-        os.path.join(self._temp_dir, 'red_skp'),
+        os.path.join(write_path_dir, 'red_skp'),
         ['bitmap-64bitMD5_11092453015575919668.png'])
     self._assert_directory_contents(
-        os.path.join(self._temp_dir, 'green_skp'),
+        os.path.join(write_path_dir, 'green_skp'),
         ['bitmap-64bitMD5_8891695120562235492.png'])
 
   def test_untiled_validate(self):
     """Same as test_untiled, but with --validate."""
-    output_json_path = os.path.join(self._temp_dir, 'actuals.json')
+    output_json_path = os.path.join(self._output_dir, 'actuals.json')
+    write_path_dir = self.create_empty_dir(
+        path=os.path.join(self._output_dir, 'writePath'))
     self._generate_skps()
     expectations_path = self._create_expectations()
     self._run_render_pictures([
         '-r', self._input_skp_dir,
         '--readJsonSummaryPath', expectations_path,
         '--validate',
-        '--writePath', self._temp_dir,
+        '--writePath', write_path_dir,
         '--writeJsonSummaryPath', output_json_path])
     expected_summary_dict = {
         "header" : EXPECTED_HEADER_CONTENTS,
@@ -326,11 +337,11 @@
     }
     self._assert_json_contents(output_json_path, expected_summary_dict)
     self._assert_directory_contents(
-        self._temp_dir, ['red_skp.png', 'green_skp.png', 'actuals.json'])
+        write_path_dir, ['red_skp.png', 'green_skp.png'])
 
   def test_untiled_without_writePath(self):
     """Same as test_untiled, but without --writePath."""
-    output_json_path = os.path.join(self._temp_dir, 'actuals.json')
+    output_json_path = os.path.join(self._output_dir, 'actuals.json')
     self._generate_skps()
     expectations_path = self._create_expectations()
     self._run_render_pictures([
@@ -352,7 +363,9 @@
 
   def test_tiled(self):
     """Generate individual tiles."""
-    output_json_path = os.path.join(self._temp_dir, 'actuals.json')
+    output_json_path = os.path.join(self._output_dir, 'actuals.json')
+    write_path_dir = self.create_empty_dir(
+        path=os.path.join(self._output_dir, 'writePath'))
     self._generate_skps()
     expectations_path = self._create_expectations()
     self._run_render_pictures([
@@ -360,7 +373,7 @@
         '--bbh', 'grid', '256', '256',
         '--mode', 'tile', '256', '256',
         '--readJsonSummaryPath', expectations_path,
-        '--writePath', self._temp_dir,
+        '--writePath', write_path_dir,
         '--writeJsonSummaryPath', output_json_path])
     expected_summary_dict = {
         "header" : EXPECTED_HEADER_CONTENTS,
@@ -375,22 +388,56 @@
     }
     self._assert_json_contents(output_json_path, expected_summary_dict)
     self._assert_directory_contents(
-        self._temp_dir,
+        write_path_dir,
         ['red_skp-tile0.png', 'red_skp-tile1.png', 'red_skp-tile2.png',
          'red_skp-tile3.png', 'red_skp-tile4.png', 'red_skp-tile5.png',
          'green_skp-tile0.png', 'green_skp-tile1.png', 'green_skp-tile2.png',
          'green_skp-tile3.png', 'green_skp-tile4.png', 'green_skp-tile5.png',
-         'actuals.json'])
+        ])
+
+  def test_tiled_mismatches(self):
+    """Same as test_tiled, but only write out mismatching images."""
+    output_json_path = os.path.join(self._output_dir, 'actuals.json')
+    mismatch_path_dir = self.create_empty_dir(
+        path=os.path.join(self._output_dir, 'mismatchPath'))
+    self._generate_skps()
+    expectations_path = self._create_expectations()
+    self._run_render_pictures([
+        '-r', self._input_skp_dir,
+        '--bbh', 'grid', '256', '256',
+        '--mode', 'tile', '256', '256',
+        '--readJsonSummaryPath', expectations_path,
+        '--mismatchPath', mismatch_path_dir,
+        '--writeJsonSummaryPath', output_json_path])
+    expected_summary_dict = {
+        "header" : EXPECTED_HEADER_CONTENTS,
+        "actual-results" : {
+            "red.skp": {
+                "tiled-images": RED_TILES,
+            },
+            "green.skp": {
+                "tiled-images": GREEN_TILES,
+            }
+        }
+    }
+    self._assert_json_contents(output_json_path, expected_summary_dict)
+    self._assert_directory_contents(
+        mismatch_path_dir,
+        ['red_skp-tile0.png', 'red_skp-tile1.png', 'red_skp-tile2.png',
+         'red_skp-tile3.png', 'red_skp-tile4.png', 'red_skp-tile5.png',
+        ])
 
   def test_tiled_writeChecksumBasedFilenames(self):
     """Same as test_tiled, but with --writeChecksumBasedFilenames."""
-    output_json_path = os.path.join(self._temp_dir, 'actuals.json')
+    output_json_path = os.path.join(self._output_dir, 'actuals.json')
+    write_path_dir = self.create_empty_dir(
+        path=os.path.join(self._output_dir, 'writePath'))
     self._generate_skps()
     self._run_render_pictures(['-r', self._input_skp_dir,
                                '--bbh', 'grid', '256', '256',
                                '--mode', 'tile', '256', '256',
                                '--writeChecksumBasedFilenames',
-                               '--writePath', self._temp_dir,
+                               '--writePath', write_path_dir,
                                '--writeJsonSummaryPath', output_json_path])
     expected_summary_dict = {
         "header" : EXPECTED_HEADER_CONTENTS,
@@ -470,10 +517,9 @@
         }
     }
     self._assert_json_contents(output_json_path, expected_summary_dict)
-    self._assert_directory_contents(self._temp_dir, [
-        'red_skp', 'green_skp', 'actuals.json'])
+    self._assert_directory_contents(write_path_dir, ['red_skp', 'green_skp'])
     self._assert_directory_contents(
-        os.path.join(self._temp_dir, 'red_skp'),
+        os.path.join(write_path_dir, 'red_skp'),
         ['bitmap-64bitMD5_5815827069051002745.png',
          'bitmap-64bitMD5_9323613075234140270.png',
          'bitmap-64bitMD5_16670399404877552232.png',
@@ -481,7 +527,7 @@
          'bitmap-64bitMD5_7325267995523877959.png',
          'bitmap-64bitMD5_2181381724594493116.png'])
     self._assert_directory_contents(
-        os.path.join(self._temp_dir, 'green_skp'),
+        os.path.join(write_path_dir, 'green_skp'),
         ['bitmap-64bitMD5_12587324416545178013.png',
          'bitmap-64bitMD5_7624374914829746293.png',
          'bitmap-64bitMD5_5686489729535631913.png',